サマリ
- 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の章を確認しながら理解しました。
今回書いたコードは以下