emahiro/b.log

Drastically Repeat Yourself !!!!

Goでファイル読み込みを調べた話

サマリ

  • io.Readerの読み込みついて調べた
  • io.ReaderをWrapして文字列置換
  • io.Readerが一括読み込みでなくstream的な動作で順次読み込みされている

io.Readerについて

refs: io - The Go Programming Language

io.ReaderはデフォルトのReadのWrap。
ioパッケージのReaderはinterfaceとしてReadが設定されている。

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

文字列置換を実装する

文字列置換を実装する前にgoにおけるinterface埋め込みを使った抽象化で文字列置換を実装します。

interface埋め込みによる抽象化

Readerが標準のReadをinterfaceとして持っているので、このReaderを埋め込むことでReadメソッドに独自の処理を加えてWrapします。

※ goは同一interfaceを定義したメソッドをstructに定義することで実装の詳細をstructに生やしたメソッドに移譲することができる。

cf. io.Reader

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

goによるinterfaceの埋め込み

type WrapRead interface {
  Read([]byte)(int, error)
}

type WrapReader struct {
   wrapper WrapRead
}

func (w *WrapReader) Read(p []byte) (int, error){
  // WrapReaderでのReadの実装の詳細を記載
} 

以下と書いても同義
※ io.Readerはデフォルトの Read([]byte) (int, error) interfaceのラップなのでわざわざinterfaceで定義し直すのは冗長。

type WrapReader struct {
  reader io.Reader
}

func (w *WrapReader) Read(p []byte) (int, error){
  // WrapReaderでのReadの実装の詳細を記載
}

文字列置換メソッドを実装する

type RewriteWriter struct {
    reader io.Reader
}

func (r *RewriteWriter) Read(p []byte) (int, error) {
    buf := make([]byte, len(p))
    n, err := r.reader.Read(buf) 
    if err != nil && err != io.EOF {
        return n, err
    }

    return copy(p, bytes.Replace(buf, []byte("0"), []byte("1"), -1)), nil
}

デフォルトのReaderを RewriteWriter のstructにreaderという変数で扱えるように埋め込んで置くことで、 RewriteWriter に生やしたReadメソッドでデフォルトのReadに実装の詳細を追加することができる。
置換するので、replaceした内容をもともとのbyteにコピーしています。

io.Readerの動作を確認する。

読み込みを確認するために以下のようなコードを書きました。

handler/main.go

func Top(w http.ResponseWriter, r *http.Request) {
    res, err := http.Get(fmt.Sprintf("http://%v:%v/data", host, port))
    if err != nil {
        fmt.Printf("request get error. err: %v", err)
    }
    body := res.Body
    defer body.Close()
  io.Copy(w, &RewriteWriter{body})
}

func Data(w http.ResponseWriter, r *http.Request) {
    var str string
    for i := 0; i < 10000; i++ {
        str = str + fmt.Sprintf("%v\n", "000")
    }

    w.Write([]byte(str))
}

10000行の文字列を置換するというものです。

動かしてみます。

$ go run main.go
# serverが起動

$ curl http://localhost:8080
111
111
111
111
# 以下同様

10000行程度ならすぐに完了してしまいますが、Readメソッドは読み込むデータを一時的に保存しておくbufferの頭から順々に読み込んでいくような動作をしているようです。
全てのデータを終端記号まで読み込んでから全データを処理するわけではないみたいです。
※ 全てのデータを終端記号まで一括で読み込む場合でから使う場合には ioutil.ReadAllメソッドを使います。

このあたりは 「Goならわかるシステムプログラミング」 の io.Readerの章を確認しながら理解しました。

今回書いたコードは以下

github.com