LoginSignup
12
7

More than 3 years have passed since last update.

golangのio.Readerは使い回しできない

Posted at

はじめに

こんにちわ、すえけん(@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は便利なのでもっと詳しくなりたいです。

12
7
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
12
7