はじめに
こんにちわ、すえけん(@sueken5)です。この記事ではgolangのio.Readerが使い回しができないことを紹介します。
io.Reader
io.Readerはioパッケージで提供されているインターフェースです。(ドキュメント)
type Reader interface {
Read(p []byte) (n int, err error)
}
具体的にこのインターフェースを満たしているものを例に挙げると
などが挙げられます。
いわゆるストリーム系のものがこのio.Readerインターフェースを満たしています。
使い回せない?
本題です。次の出力は何になるでしょうか?
package main
import (
"fmt"
"io/ioutil"
"log"
"strings"
)
func main() {
r := strings.NewReader("hello world")
a, err := ioutil.ReadAll(r)
if err != nil {
log.Fatal(err)
}
fmt.Println("a is", string(a))
b, err := ioutil.ReadAll(r)
if err != nil {
log.Fatal(err)
}
fmt.Println("b is", string(b))
}
予想される出力
a is hello world
b is hello world
実際の出力
a is hello world
b is
実際の出力だとb
が空になってしまっています。なぜでしょうか?
Read()では何が起こっている?
今回サンプルでstrings.Reader
を見てみます
type Reader struct {
s string
i int64 // current reading index
prevRune int // index of previous rune; or < 0
}
func (r *Reader) Read(b []byte) (n int, err error) {
if r.i >= int64(len(r.s)) {
return 0, io.EOF
}
r.prevRune = -1
n = copy(b, r.s[r.i:])
r.i += int64(n) // <= 内部でストリームのインデックスを持っている
return
}
内部でRead
が呼ばれるたびにストリームに対してのインデックスをカウントアップしています。
なので次の場合
a, err := ioutil.ReadAll(r)
if err != nil {
log.Fatal(err)
}
fmt.Println("a is", string(a))
b, err := ioutil.ReadAll(r) //すでにストリームの最後までいってるから何も返さない
if err != nil {
log.Fatal(err)
}
2回目にReadAllしたい場合にストリームが最後まで読み切られているので何も返さなかったということになります。
使い回す方法
使い回す方法としてio.TeeReader
があります。(ドキュメント)
先ほどの失敗例をTeeReaderを使って修正すると次のようになります。
package main
import (
"fmt"
"io/ioutil"
"log"
"io"
"strings"
"bytes"
)
func main() {
r := strings.NewReader("hello world")
bBuf := new(bytes.Buffer)
aBuf := io.TeeReader(r, bBuf)
a, err := ioutil.ReadAll(aBuf)
if err != nil {
log.Fatal(err)
}
fmt.Println("a is", string(a))
b, err := ioutil.ReadAll(bBuf)
if err != nil {
log.Fatal(err)
}
fmt.Println("b is", string(b))
}
出力
a is hello world
b is hello world
最後に
io.Readerは便利なのでもっと詳しくなりたいです。