Skip to content

Go ioパッケージ Walkthrough

2023/02/02

Go

目次

目次

  1. io パッケージについて
  2. Reader
    1. ReadAll
    2. ReadAtLeast
    3. ReadFull
  3. Writer
    1. WriteString
    2. Copy
    3. CopyBuffer
    4. CopyN
  4. PipeReader/PipeWriter
    1. Pipe
  5. TeeReader

io パッケージについて

io package は、ディスク上のファイルからキーボード入力まであらゆる入力と出力のインタフェースを提供しています。

$ gocloc --not-match=test io
-------------------------------------------------------------------------------
Language                     files          blank        comment           code
-------------------------------------------------------------------------------
Go                              12            212            673           1092
-------------------------------------------------------------------------------
TOTAL                           12            212            673           1092
-------------------------------------------------------------------------------

Reader

ReadAll

io > ReadAll

最初に 512 バイトのバッファを確保し、EOF に到達するまでキャパシティを大きく取りながら読みみます。 サイズの取り方は、Reader interface の Read で実装しています。

ファイルの場合は事前に分かるため buffer の大きさだけ読みます。

file > Read

io > fs > FileInfo

文字列やバイトの場合は、長さがわからないため、徐々にキャパシティを大きくしながら読みます。

strings > Read

bytes > Read

Read メソッドにキャパシティの取り方を任せることで、ケースごとに効率の良い読み取りの実装がされています。

ReadAtLeast

io > ReadAtLeast

指定した位置まで Buffer にデータを読みます。

途中まで読むケースにおいて、予め位置がわかっていれば、Read のキャパシティを徐々に大きくする処理を抑えることができます。

実用的な利用ケースは思いつかないのですが、外部ファイルなど非常に大きなサイズになる可能性があるケースにおいて安全に扱うことができそうです。

ReadFull

io > ReadFull

Buffer の長さがキャパシティと同一、つまり Buffer にデータが満たされていることをが保証される。len!=cap の場合は、EOF がエラーで返されます。

データが正しく読まれることを保証しコードを単純にできます。

b := []byte("0123")
	r := bytes.NewReader(b)

	buf := make([]byte, 10)
	n, err := io.ReadFull(r, buf)

	if err != nil {
		log.Fatal(err) // EOF error
	}
b := []byte("0123")
	r := bytes.NewReader(b)

	buf := make([]byte, 4)
	n, err := io.ReadFull(r, buf)

	if err != nil { // OK
		log.Fatal(err)
	}

Writer

WriteString

io > WriteString

Writer の Write メソッドを呼び出しです。実装されていない場合は、文字列をバイトに入れ Write メソッドを実行します。

Copy

io > Copy

Copy は Buffer を呼び出しごとに割り当てますが、CopyBuffer を利用し自分で割り当てるることもできます。

// https://cs.opensource.google/go/go/+/refs/tags/go1.19.5:src/io/io.go;l=415;bpv=1;bpt=1
if buf == nil {
    size := 32 * 1024
    if l, ok := src.(*LimitedReader); ok && int64(size) > l.N {
        if l.N < 1 {
            size = 1
        } else {
            size = int(l.N)
        }
    }
    buf = make([]byte, size)
}

CopyBuffer

io > CopyBuffer

Reader が WriteTo メソッドが実装されている場合はそれを利用します。

strings > Strings WriteTo では、io の WriteString を呼び出し、Writer の WriteString メソッドを呼びます。

Writer に ReadFrom メソッドがある場合はそれを利用します。

どちらも実装されていない場合は、Reader の Read と Writer の Write を利用します。

CopyN

io > CopyN

Copy は外部ファイルなど最大バイト数がわからず、想定以上に読み込まれる可能性があります。

CopyN では書き込むバイト数を指定できます。そのため、大きなキャパシティになる可能性がある場合でも安全に扱うことができます。

PipeReader/PipeWriter

Pipe

io > pipe

The data is copied directly from the Write to the corresponding Read (or Reads); there is no internal buffering. (go doc)

バッファリングなしで直接扱えるのでメモリ効率が良くなるようです。

Examples For Using io.Pipe in Go - zupzup

こちらのブログを読んで理解したのですが、例えば HTTP リクエストの結果を PipeWriter で取得し、直接 PipeReader に送ることで、余計なメモリ確保が不要になるという解釈をしました。

TeeReader

io > TeeReader

Reader をラップした新しいリーダーを返します。

TeeReader の Read は、渡した Reader の中身を読み Writer で書き込むため、もとの Reader に影響がありません。

io > TeeReader > Read

net/http でレスポンスの内容を確認したいなら io.TeeReader を使おう』 のような使い方ができるようです。