いろいろ備忘録日記

主に .NET とか Go とか Flutter とか Python絡みのメモを公開しています。

Goメモ-38 (io.Readerインターフェース, Readers, Tour of Go)

概要

Tour of Go の - Readers についてのサンプル。

tour.golang.org

Goのio.Readerインターフェースは、何かを読み取る基本的なインターフェースです。

このインターフェースを実装している型はとてもいっぱいあります。

インターフェースの定義は以下のようになっています。

(io.go ファイルより)

type Reader interface {
    Read(p []byte) (n int, err error)
}

とてもシンプル。綺麗ですよね。ReaderがあるってことはWriterも当然あります。

type Writer interface {
    Write(p []byte) (n int, err error)
}

この他にも、io.goファイルを見るとインターフェースがいっぱいあります。見てるととても面白いです。

Golandとか使っている場合、インターフェースや型の横にアノテーションで、実装しているインターフェースとか実装されている型とかを確認できるので、いい勉強にもなります。

どれも、小さなインターフェースで構築してあって、組み合わせやすいようになっています。

で、Tour of Goでサンプルとして記載されているものとして、strings.Reader があります。

この型もio.Readerインターフェースを実装しているので、当然 Readメソッドを持っています。

文字列をストリームのようにして扱いたいときによく利用します。

ちなみに、strings.Reader は、ioパッケージの以下のインターフェースを実装しています。

  • ByteReader
  • ByteScanner
  • ReadSeeker
  • Reader
  • ReaderAt
  • RuneReader
  • RuneScanner
  • Seeker
  • WriteTo

文字列のストリームですので、閉じる必要がない。なので、strings.Readerio.Closerは実装していないってことですね。逆に os.Fileとかになると内部リソースを開放する必要があるので、io.Closerを実装しているって感じです。

サンプル

package tutorial

import (
    "errors"
    "fmt"
    "io"
    "strings"
)

// Reader は、 Tour of Go - Readers (https://tour.golang.org/methods/21) の サンプルです。
func Reader() error {
    // ------------------------------------------------------------
    // Go言語の io.Reader インターフェース
    // io.Readerインターフェースは、何かを読み取る基本的なインターフェース
    // このインターフェースを実装している型はとても多い。
    //
    // io.Reader インターフェースには、以下の定義がある
    //   - Read(b []byte) (n int, err error)
    //
    // 基本的なものとして、 strings.Reader がある
    // これは、他の言語の StringReader と同じような使い方が出来る
    //
    // n に読み込んだバイト数が設定されるので、それでバッファから取り出して
    // 末尾まで読み込むと、 err に io.EOF が設定されるので
    // それを判断して処理を中断するというのが基本パターン.
    // ------------------------------------------------------------
    var (
        message   = "hello world"
        reader    = strings.NewReader(message)
        chunkSize = 4
        buf       = make([]byte, chunkSize)
        results   = make([]byte, 0, len(message))
    )

    // 4 バイトずつ読み込み
    for {
        n, err := reader.Read(buf)
        fmt.Printf("[reader] read: %dbytes\terror: %v\tvalue: %s\n", n, err, buf[:n])

        if n != 0 {
            results = append(results, buf[:n]...)
        }

        if errors.Is(err, io.EOF) {
            break
        }
    }

    fmt.Printf("[result] %s\n", string(results))

    // (補足) 上の例ではRead用のバッファと結果用のバッファの2つを使っているが
    //        一つのバッファで読み込み続けることも可能。2つバッファ使った方が
    //        わかりやすいので個人的にはあまり使わない。
    var (
        from      = 0
        to        = 0
        readBytes = 0
    )

    // 1 バイトずつ読み込み
    reader = strings.NewReader(message)
    buf = make([]byte, len(message))
    for {
        from, to = readBytes, readBytes+1
        if len(message) < to {
            break
        }

        n, err := reader.Read(buf[from:to])
        readBytes += n

        if n == 0 || err != nil {
            break
        }
    }

    fmt.Printf("[result] %d bytes. (%s)\n", readBytes, string(buf))

    return nil
}

try-golang/tutorial_gotour_25_reader.go at master · devlights/try-golang · GitHub

実行すると以下な感じ。

[Name] "tutorial_gotour_reader"
[reader] read: 4bytes   error: <nil>    value: hell
[reader] read: 4bytes   error: <nil>    value: o wo
[reader] read: 3bytes   error: <nil>    value: rld
[reader] read: 0bytes   error: EOF      value: 
[result] hello world
[result] 11 bytes. (hello world)

過去の記事については、以下のページからご参照下さい。

  • いろいろ備忘録日記まとめ

devlights.github.io

サンプルコードは、以下の場所で公開しています。

  • いろいろ備忘録日記サンプルソース置き場

github.com

github.com

github.com