Carpe Diem

備忘録

Goのioパッケージのメソッドを図示

概要

Goのioパッケージにはio.Readerio.Writerのインタフェースを有効活用するため以下のような便利なメソッドが用意されています。

しかし普段から使っていないと、これらメソッドの方が簡単にかけるのに忘れて遠回りな書き方をしがちです。
なのでイメージ化して覚えやすいようにします。

図示してみる

ASCII.jp:低レベルアクセスへの入り口(3):io.Reader後編 (2/2)

こちらを参考に図示します。

f:id:quoll00:20200603001422p:plain

ポイントは以下です。

  • 丸いコネクタが io.Reader の送受信
  • 三角形のコネクタが io.Writer の送受信
  • データはすべて左から右に流れる

例えばos.Stdinos.Stdoutはこうなります。

f:id:quoll00:20200603001708p:plain

io.Copy

io.Copyはio.Readerからデータを読み込み、io.Writerへと書き出します。

f:id:quoll00:20200603002516p:plain

なのでos.Stdin、os.Stdoutとそのままくっつけられます。

f:id:quoll00:20200603002757p:plain

実際のコード

コードに落とし込むと以下のようになります。

func main() {
        io.Copy(os.Stdout, os.Stdin)
}

キーボード(標準入力)にhogeと入れると、コンソール(標準出力)に吐き出されます。

$ go run main.go
hoge  # hogeと入力
hoge

io.Pipe

io.Copyと逆で、io.Writerで書き込まれたらio.Readerから読み出せるようになります。

f:id:quoll00:20200603003431p:plain

例えばオブジェクトをjson変換したものを標準出力に吐き出したい場合以下のように組み立てます。

f:id:quoll00:20200603004745p:plain

実際のコード

コードに落とし込むと以下のようになります。

type Person struct {
        Name string `json:"name"`
        Age  int    `json:"age"`
}

func main() {
        p := Person{"Alice", 20}
        pr, pw := io.Pipe()
        go func() {
                json.NewEncoder(pw).Encode(&p)
                pw.Close()
        }()
        io.Copy(os.Stdout, pr)
}

io.Pipeは内部バッファを持たないので必ずgoroutineで扱いましょう。

$ go run main.go
{"name":"Alice","age":20}

io.MultiReader

io.MultiReaderは複数のio.Readerを1つにまとめられます。
引数の順にconcatしていきます。

f:id:quoll00:20200603010445p:plain

実際のコード

func main() {
        header := strings.NewReader("----- HEADER -----\n")
        content := strings.NewReader("----- CONTENT -----\n")
        footer := strings.NewReader("----- FOOTER -----\n")

        mr := io.MultiReader(header, content, footer)
        io.Copy(os.Stdout, mr)
}

こんな感じに繋げてます。

f:id:quoll00:20200603011719p:plain

$ go run main.go
----- HEADER -----
----- CONTENT -----
----- FOOTER -----

io.MultiWriter

io.MultiWriterは1つのio.Writerを元に複数のio.Writerへ書き出します。

f:id:quoll00:20200603012054p:plain

実際のコード

io.MultiWriterを利用することで出力先を標準出力とファイルの複数にしたりできます。

func main() {
        f, err := os.OpenFile("out", os.O_RDWR|os.O_CREATE, 0755)
        if err != nil {
                log.Fatal(err)
        }
        defer f.Close()

        mr := io.MultiWriter(os.Stdout, f)
        fmt.Fprintf(mr, "hello\n")
}

こんな感じに繋げてます。

f:id:quoll00:20200603013214p:plain

$ go run main.go
hello
$ cat out
hello

io.TeeReader

io.TeeReaderは通常のio.Readerを読み込まれたら別のio.Writerに書き出すio.Readerへと変換します。

f:id:quoll00:20200603013559p:plain

実際のコード

func main() {
        reader := strings.NewReader("Hello\n")

        var buffer bytes.Buffer
        teeReader := io.TeeReader(reader, &buffer)

        io.Copy(os.Stdout, teeReader)
        fmt.Printf(buffer.String())
}

こんな感じに繋げてます。

f:id:quoll00:20200603014725p:plain

$ go run main.go
Hello
Hello

先程のio.MultiWriterは書き込んだらio.Writerへと吐き出されましたが、こちらは読み込んだらio.Writerへ吐き出されます。

まとめ

ioパッケージのメソッドは図で理解するとパズルを組み立てるように活用することができます。