倭マン's BLOG

くだらない日々の日記書いてます。 たまにプログラミング関連の記事書いてます。 書いてます。

Go 言語の io パッケージをのパッケージ関数を使ってみる

Go 言語のいろいろなパッケージを使ってみるシリーズ(目次)。 以前の2つの記事

で io パッケージに定義されている型や関数を使ってみましたが、今回は io パッケージに定義されている残りのパッケージ関数を使っていきます。 まぁ、関数のシグニチャ(名前や引数)を見れば大体動作は分かりそうな関数ばっかりだし、パッケージドキュメントに簡潔な説明とサンプルコードが載ってますが。

【この記事の内容】

io パッケージに定義されている関数

今回見ていく io パッケージの関数:

// コピー関連
func Copy(dst Writer, src Reader) (written int64, err error)
func CopyBuffer(dst Writer, src Reader, buf []byte) (written int64, err error)
func CopyN(dst Writer, src Reader, n int64) (written int64, err error)

// 読み込み関連
func ReadAtLeast(r Reader, buf []byte, min int) (n int, err error)
func ReadFull(r Reader, buf []byte) (n int, err error)

// 書き出し関連
func WriteString(w Writer, s string) (n int, err error)

コピー関連

io.Copy 関数
io.Copy 関数は、第2引数の io.Reader から io.EOF に達するまで内容を読み込み、第1引数の io.Writer に書き出します:

  src := strings.NewReader("Hello, world!")
  var dst bytes.Buffer

  // io.Copy 関数
  io.Copy(os.Stdout, src)
  // Output:
  // Hello, world!

引数の順序は、通常の変数への値の代入「x := "Hello, world"」と同じ順序になっています。 返り値はコピーしたバイト数とコピー時のエラー(なければ nil)です:

  src := strings.NewReader("Hello, world!")
  var dst bytes.Buffer

  // io.Copy 関数
  n, err := io.Copy(&dst, src)
  if err != nil { log.Fatal(err) }

  fmt.Printf("%s (%d bytes)", dst.String(), n)
  // Output:
  // Hello, world! (13 bytes)

読み込みは io.EOF に達するまでされますが、コピーが成功した場合には第2返り値のエラーは io.EOF ではなく nil です。

引数の io.Reader が io.WriterTo インターフェースを実装していればその WriteTo メソッドを使い、引数の io.Writer が io.ReaderFrom インターフェースを実装していればその ReadFrom メソッドが使われます(WriterTo 優先)。

io.CopyBuffer 関数
io.CopyBuffer 関数は、基本的には io.Copy 関数と同じですが、コピー時に(必要なら)第3引数のバイトスライスをバッファとして使います:

  src := strings.NewReader("Hello, world!")
  var dst bytes.Buffer
  buf := make([]byte, 1024)

  // io.CopyBuffer 関数
  n, err := io.CopyBuffer(&dst, src, buf)
  if err != nil { log.Fatal(err) }

  fmt.Printf("%s (%d bytes)", dst.String(), n)
  // Output:
  // Hello, world! (13 bytes)

おそらく引数の io.Reader や io.Writer が io.WriterTo や io.ReaderFrom を実装していれば、バッファは使われないかと思います。

指定したバイトスライスが nil なら別にバッファが割り当てられ、長さが0ならパニックを起こします:

  io.CopyBuffer(&dst, src, nil)  // 新たにバッファが割り当てられる
  io.CopyBuffer(&dst, src, make([]byte, 0))  // パニックを起こす

io.CopyN 関数
io.CopyN 関数は指定した第2引数の io.Reader から第1引数の io.Writer へ、第3引数で指定したバイト数だけコピーします:

  src := strings.NewReader("Hello, world!")
  var dst bytes.Buffer

  // io.CopyN 関数
  n, err := io.CopyN(&dst, src, 5)
  if err == io.EOF {
    fmt.Printf("%s (%d bytes)", dst.String(), n)
		
  } else if err != nil{
    log.Fatalf("%d bytes are copied: %v", n, err)
		
  } else {
    fmt.Println(dst.String())  // 5バイトを読み取って書き出す
  }
  // Output:
  // Hello

io.Reader から読み込めるバイト数が指定したより少ない場合、io.EOF が返されます:

  src := strings.NewReader("Hello, world!")  // 13バイト
  var dst bytes.Buffer

  // io.CopyN 関数
  n, err := io.CopyN(&dst, src, 20)  // 20 バイトをコピー
  if err == io.EOF {
    // 読み込めるバイト数が少ない場合
    fmt.Printf("%s (%d bytes)", dst.String(), n)
		
  } else if err != nil{
    log.Fatal(err)
		
  } else {
    fmt.Println(dst.String())  // 5バイトを読み取って書き出す
  }
  // Output:
  // Hello, world! (13 bytes)

io.EOF が返された場合でも、ある分のバイトはコピーされているようです。

引数の io.Writer が io.ReaderFrom インターフェースを実装していれば、その ReadFrom メソッドを使います。

読み込み関連

io.ReadAtLeast 関数
io.ReadAtLeast 関数は、第1引数の io.Reader から第2引数のバイトスライスへ、第3引数で指定したバイト数以上のバイトを読み込みます。 指定したバイト数を読み込めなかった場合は io.ErrUnexpectedEOF エラーを返します(ただし、読み込んだバイト数が0の場合は io.EOF を返します):

  r := strings.NewReader("Hello, world!")  // 13バイト
  buf := make([]byte, 1024)

  // io.ReadAtLeast 関数
  n, err := io.ReadAtLeast(r, buf, 10)  // 10バイト以上を読み込む
  if err == io.ErrUnexpectedEOF {
    fmt.Printf("%s (%d bytes)", string(buf), n)

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

  } else {
    // 今の場合はここが実行される
    fmt.Println(string(buf))
  }
  // Output:
  // Hello, world!

このコードで io.ReadAtLeast 関数の第3引数を15にすると io.ErrUnexpectedEOF が返されます:

  r := strings.NewReader("Hello, world!")  // 13バイト
  ...

  // io.ReadAtLeast 関数
  n, err := io.ReadAtLeast(r, buf, 15)  // 15バイト以上を読み込む
  if err == io.ErrUnexpectedEOF {
    // 今の場合はここが実行される
    fmt.Printf("%s (%d bytes)", string(buf), n)

  } else if err != nil{
    ...
  }
  // Output:
  // Hello, world! (13 bytes)

指定したバイト数が第2引数のバイトスライスの長さより大きければ io.ErrShortBuffer エラーが返されます:

  n, err := io.ReadAtLeast(r, make([]byte, 20), 25)
  // err に io.ErroShortBuffer がセットされる

io.ReadFull 関数
io.ReadFull 関数は、上記の io.ReadAtLeast 関数で第3引数のバイト数に第2引数のバイトスライスの長さを指定したものと同じです(実装は知りませんが):

  r := strings.NewReader("Hello, world!")  // 13バイト
  buf := make([]byte, 10)

  // io.ReadFull 関数
  n, err := io.ReadFull(r, buf)
  if err == io.ErrUnexpectedEOF {
    fmt.Printf("%s (%d bytes)", string(buf), n)

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

  } else {
    // 今の場合はここが実行される
    fmt.Println(string(buf))
  }
  // Output:
  // Hello, wor

書き出し関連

io.WriteString 関数
io.WriteString 関数は、第1引数の io.Writer に文字列を書き出します。 引数の io.Writer が(適当なシグニチャの) WriteString メソッドを持っていればそれを使います。 そうでなければ Write メソッドを使いますが、一度しか Write メソッドを呼び出しません。 返り値は書き込んだバイト数とエラー(なければ nil)です:

  var buf bytes.Buffer

  // io.WriteString 関数
  io.WriteString(&buf, "Hello, world!")

  fmt.Println(buf.String())
  // Output:
  // Hello, world!

この io.WriteString 関数は、WriteString メソッドを持つ io.Writer に対しては、文字列をバイトスライスへ変換して io.Writer の Write メソッドで書き出すよりも効率的です:

  // io.WriteString は以下の Write メソッド呼び出しより効率的
  &buf.Write([]byte("Hello, world!"))

今回は io パッケージに定義されているパッケージ関数を使ってみました。 基本的な使い方は名前などから分かりますが、返されるエラーは少し注意が必要かも知れませんね。 あと、io.Copy 関数はまぁよく使いそうですが、io.WriteString 関数はついつい文字列をバイトスライスに変換して Write メソッドで済ませてしまいそうなので、意識して使っていった方がよさそうです。

次回は入出力をきちんと扱う場合に io パッケージよりも使いそうな bufio パッケージを使ってみる予定。

プログラミング言語Go (ADDISON-WESLEY PROFESSIONAL COMPUTING SERIES)

プログラミング言語Go (ADDISON-WESLEY PROFESSIONAL COMPUTING SERIES)