Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

[インデックス 14135] ファイルの概要

このコミットは、Go言語の標準ライブラリであるbytesパッケージとstringsパッケージのReader型に、io.WriterToインターフェースの実装を追加するものです。これにより、bytes.Readerおよびstrings.Readerのコンテンツを、効率的に任意のio.Writerへ書き出す機能が提供されます。

コミット

  • コミットハッシュ: eae25d430d6d6c40e129c50ed4a931858be8ffb4
  • 作者: Evan Shaw chickencha@gmail.com
  • コミット日時: 2012年10月12日 金曜日 14:43:50 +1100

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/eae25d430d6d6c40e129c50ed4a931858be8ffb4

元コミット内容

bytes, strings: add (*Reader).WriteTo

Fixes #4031.

R=golang-dev, bradfitz, remyoudompheng, r, dave
CC=golang-dev
https://golang.org/cl/6632046

変更の背景

この変更は、Go言語のIssue #4031「bytes.Readerstrings.ReaderWriteToメソッドを追加する」に対応するものです。

Go言語のioパッケージには、データの読み書きに関する標準的なインターフェースが定義されています。io.Readerはデータを読み出すためのインターフェースであり、io.Writerはデータを書き込むためのインターフェースです。これらとは別に、io.WriterToインターフェースが存在します。これは、自身が持っているデータを直接io.Writerに書き込む能力を持つ型が実装するインターフェースです。

type WriterTo interface {
    WriteTo(w Writer) (n int64, err error)
}

io.Copy関数は、io.Readerからio.Writerへデータをコピーする際に非常に便利ですが、内部的には最適化のためにio.ReaderFromio.WriterToインターフェースの存在をチェックします。もしio.Readerio.WriterToを実装している場合、io.CopyはそのWriteToメソッドを呼び出すことで、より効率的なデータ転送を実現できます。これは、io.Copyが内部でバッファリングを行う必要がなくなり、データソース自身が最適な方法でデータを書き込み先に転送できるためです。

bytes.Readerstrings.Readerは、それぞれバイトスライスと文字列からデータを読み出すためのio.Readerの実装です。これらの型がio.WriterToを実装していなかったため、io.Copyを使用した場合に、内部でバッファリングを伴う一般的なRead/Writeループが実行されていました。これは、特に大きなデータの場合にパフォーマンスのボトルネックとなる可能性がありました。

Issue #4031は、この非効率性を解消し、bytes.Readerstrings.Readerからのデータ転送を最適化するために、WriteToメソッドの追加を提案していました。このコミットは、その提案に応える形で、両方のReader型にWriteToメソッドを実装し、io.Copyなどの操作におけるパフォーマンス向上を図るものです。

前提知識の解説

io.Readerインターフェース

io.Readerは、Go言語でデータを読み出すための最も基本的なインターフェースです。

type Reader interface {
    Read(p []byte) (n int, err error)
}

Readメソッドは、pに最大len(p)バイトのデータを読み込み、読み込んだバイト数nとエラーを返します。データがこれ以上ない場合はio.EOFエラーを返します。

io.Writerインターフェース

io.Writerは、Go言語でデータを書き込むための最も基本的なインターフェースです。

type Writer interface {
    Write(p []byte) (n int, err error)
}

Writeメソッドは、pからデータを書き込み、書き込んだバイト数nとエラーを返します。

io.WriterToインターフェース

io.WriterToは、自身が持っているデータを直接io.Writerに書き込む能力を持つ型が実装するインターフェースです。

type WriterTo interface {
    WriteTo(w Writer) (n int64, err error)
}

WriteToメソッドは、レシーバ(この場合はReader)のデータをwに書き込み、書き込んだバイト数nとエラーを返します。このインターフェースを実装することで、io.Copyのような関数が、より効率的なデータ転送パスを選択できるようになります。

bytes.Readerstrings.Reader

  • bytes.Reader: バイトスライス([]byte)からデータを読み出すためのio.Readerの実装です。読み出し位置をシーク(移動)できるため、io.Seekerインターフェースも実装しています。
  • strings.Reader: 文字列(string)からデータを読み出すためのio.Readerの実装です。こちらも読み出し位置をシークできるため、io.Seekerインターフェースを実装しています。

これらのReaderは、メモリ上のデータソースから効率的にデータを読み出すために使用されます。

io.Copy関数

io.Copy関数は、io.Readerからio.Writerへデータをコピーするためのユーティリティ関数です。

func Copy(dst Writer, src Reader) (written int64, err error)

io.Copyは、内部でsrcio.WriterToを実装しているか、またはdstio.ReaderFromを実装しているかをチェックします。もしsrcio.WriterToを実装していれば、src.WriteTo(dst)を呼び出します。これにより、src自身が最も効率的な方法でデータをdstに転送できます。この最適化パスは、特に大きなデータを扱う場合に、中間バッファリングのオーバーヘッドを削減し、パフォーマンスを向上させます。

技術的詳細

このコミットでは、bytes.Readerstrings.ReaderのそれぞれにWriteToメソッドが追加されています。実装は両者で非常に似ていますが、bytes.Readerはバイトスライスを直接扱い、strings.Readerは文字列を扱うため、細部が異なります。

bytes.ReaderWriteTo実装

src/pkg/bytes/reader.goに以下のWriteToメソッドが追加されました。

// WriteTo implements the io.WriterTo interface.
func (r *Reader) WriteTo(w io.Writer) (n int64, err error) {
	r.prevRune = -1 // prevRuneをリセット
	if r.i >= len(r.s) { // 読み出し位置がデータの終端に達している場合
		return 0, io.EOF // 0バイトを返し、EOFエラー
	}
	b := r.s[r.i:] // 現在の読み出し位置から残りのバイトスライスを取得
	m, err := w.Write(b) // Writerに直接バイトスライスを書き込む
	if m > len(b) { // 書き込まれたバイト数が残りのデータ長を超える場合 (異常なケース)
		panic("bytes.Reader.WriteTo: invalid Write count") // パニック
	}
	r.i += m // 読み出し位置を更新
	n = int64(m) // 書き込まれたバイト数をint64に変換
	if m != len(b) && err == nil { // 全てのデータが書き込まれていないのにエラーがない場合
		err = io.ErrShortWrite // io.ErrShortWriteエラーを返す
	}
	return
}
  • r.prevRune = -1: bytes.Readerio.RuneScannerも実装しており、ルーンの読み出し状態を管理するprevRuneフィールドを持っています。WriteToは内部的な読み出し位置を直接進めるため、この状態をリセットします。
  • if r.i >= len(r.s): Readerの内部ポインタr.iが、元となるバイトスライスr.sの長さを超えている場合、つまり読み出すデータが残っていない場合は、0バイトとio.EOFを返します。
  • b := r.s[r.i:]: 現在の読み出し位置r.iから、残りのすべてのデータをbという新しいバイトスライスとして取得します。
  • m, err := w.Write(b): 取得したbを、引数で渡されたio.Writer wに直接書き込みます。これがWriteToの核心であり、効率的なデータ転送を実現します。
  • if m > len(b): w.Writeが、渡されたバイトスライスbの長さよりも多くのバイトを書き込んだと報告した場合、これは異常な状態であるためパニックを起こします。
  • r.i += m: 実際に書き込まれたバイト数mだけ、Readerの内部ポインタr.iを進めます。
  • n = int64(m): io.WriterToインターフェースの戻り値の型に合わせて、mint64にキャストしてnに代入します。
  • if m != len(b) && err == nil: w.Writeが、渡されたすべてのバイトを書き込まなかったにもかかわらず、エラーを返さなかった場合、これは「短縮書き込み(short write)」と呼ばれる状態です。この場合、io.ErrShortWriteエラーを返します。これは、io.Writerの実装が部分的な書き込みを許容しつつも、エラーを返さない場合に、呼び出し元にその事実を伝えるための標準的なGoの慣習です。

strings.ReaderWriteTo実装

src/pkg/strings/reader.goに以下のWriteToメソッドが追加されました。

// WriteTo implements the io.WriterTo interface.
func (r *Reader) WriteTo(w io.Writer) (n int64, err error) {
	r.prevRune = -1 // prevRuneをリセット
	if r.i >= len(r.s) { // 読み出し位置がデータの終端に達している場合
		return 0, io.EOF // 0バイトを返し、EOFエラー
	}
	s := r.s[r.i:] // 現在の読み出し位置から残りの文字列を取得
	m, err := io.WriteString(w, s) // io.WriteStringを使ってWriterに直接文字列を書き込む
	if m > len(s) { // 書き込まれたバイト数が残りの文字列長を超える場合 (異常なケース)
		panic("strings.Reader.WriteTo: invalid WriteString count") // パニック
	}
	r.i += m // 読み出し位置を更新
	n = int64(m) // 書き込まれたバイト数をint64に変換
	if m != len(s) && err == nil { // 全てのデータが書き込まれていないのにエラーがない場合
		err = io.ErrShortWrite // io.ErrShortWriteエラーを返す
	}
	return
}

strings.ReaderWriteToは、bytes.Readerとほぼ同じロジックですが、バイトスライスではなく文字列を扱うため、w.Write(b)の代わりにio.WriteString(w, s)を使用しています。io.WriteStringは、io.Writerio.StringWriterインターフェースを実装している場合に最適化されたパスを提供し、そうでない場合はw.Write([]byte(s))にフォールバックします。これにより、文字列のコピーを最小限に抑えつつ、効率的な書き込みを実現します。

テストの追加

このコミットでは、bytes/reader_test.gostrings/reader_test.goにそれぞれTestReaderWriteToTestWriteToというテスト関数が追加されています。これらのテストは、WriteToメソッドが期待通りに動作し、正確なバイト数を返し、エラー処理が適切に行われることを確認します。特に、io.EOFio.ErrShortWriteのシナリオは直接テストされていませんが、一般的なデータ転送のケースが網羅されています。

コアとなるコードの変更箇所

src/pkg/bytes/reader.go

--- a/src/pkg/bytes/reader.go
+++ b/src/pkg/bytes/reader.go
@@ -10,7 +10,7 @@ import (
 	"unicode/utf8"
 )

-// A Reader implements the io.Reader, io.ReaderAt, io.Seeker,
+// A Reader implements the io.Reader, io.ReaderAt, io.WriterTo, io.Seeker,
 // io.ByteScanner, and io.RuneScanner interfaces by reading from
 // a byte slice.
 // Unlike a Buffer, a Reader is read-only and supports seeking.
@@ -121,5 +121,24 @@ func (r *Reader) Seek(offset int64, whence int) (int64, error) {
 	return abs, nil
 }

+// WriteTo implements the io.WriterTo interface.
+func (r *Reader) WriteTo(w io.Writer) (n int64, err error) {
+	r.prevRune = -1
+	if r.i >= len(r.s) {
+		return 0, io.EOF
+	}
+	b := r.s[r.i:]
+	m, err := w.Write(b)
+	if m > len(b) {
+		panic("bytes.Reader.WriteTo: invalid Write count")
+	}
+	r.i += m
+	n = int64(m)
+	if m != len(b) && err == nil {
+		err = io.ErrShortWrite
+	}
+	return
+}
+
 // NewReader returns a new Reader reading from b.
 func NewReader(b []byte) *Reader { return &Reader{b, 0, -1} }

src/pkg/strings/reader.go

--- a/src/pkg/strings/reader.go
+++ b/src/pkg/strings/reader.go
@@ -10,7 +10,7 @@ import (
 	"unicode/utf8"
 )

-// A Reader implements the io.Reader, io.ReaderAt, io.Seeker,
+// A Reader implements the io.Reader, io.ReaderAt, io.Seeker, io.WriterTo,
 // io.ByteScanner, and io.RuneScanner interfaces by reading
 // from a string.
 type Reader struct {
@@ -120,6 +120,25 @@ func (r *Reader) Seek(offset int64, whence int) (int64, error) {
 	return abs, nil
 }

+// WriteTo implements the io.WriterTo interface.
+func (r *Reader) WriteTo(w io.Writer) (n int64, err error) {
+	r.prevRune = -1
+	if r.i >= len(r.s) {
+		return 0, io.EOF
+	}
+	s := r.s[r.i:]
+	m, err := io.WriteString(w, s)
+	if m > len(s) {
+		panic("strings.Reader.WriteTo: invalid WriteString count")
+	}
+	r.i += m
+	n = int64(m)
+	if m != len(s) && err == nil {
+		err = io.ErrShortWrite
+	}
+	return
+}
+
 // NewReader returns a new Reader reading from s.
 // It is similar to bytes.NewBufferString but more efficient and read-only.
 func NewReader(s string) *Reader { return &Reader{s, 0, -1} }

コアとなるコードの解説

上記の変更箇所は、bytes.Readerstrings.Readerの型定義コメントにio.WriterToが追加されたことと、それぞれの型にWriteToメソッドが実装されたことを示しています。

bytes.ReaderWriteToメソッド

  1. r.prevRune = -1: bytes.Readerはルーン(Unicodeコードポイント)を読み取るための内部状態prevRuneを持っています。WriteToはバイト単位でデータを処理するため、ルーンの読み取り状態をリセットします。
  2. if r.i >= len(r.s): r.iは現在の読み取り位置を示します。もし読み取り位置が元のバイトスライスr.sの長さを超えている場合、読み取るべきデータは残っていません。この場合、0バイトを書き込み、io.EOF(End Of File)エラーを返します。これは、データソースの終端に達したことを示す標準的な方法です。
  3. b := r.s[r.i:]: 現在の読み取り位置r.iから、バイトスライスr.sの残りの部分を新しいスライスbとして取得します。このスライスbが、io.Writerに書き込まれるデータ本体となります。
  4. m, err := w.Write(b): 取得したバイトスライスbを、引数として渡されたio.WriterインターフェースwWriteメソッドに渡して書き込みを実行します。mは実際に書き込まれたバイト数、errは書き込み中に発生したエラーです。
  5. if m > len(b): w.Writeが、渡されたバイトスライスbの長さよりも多くのバイトを書き込んだと報告した場合、これは論理的にありえない異常な状態です。このような場合、プログラムの整合性を保つためにパニック(実行時エラー)を発生させます。
  6. r.i += m: 実際にwに書き込まれたバイト数mだけ、bytes.Readerの内部読み取り位置r.iを進めます。これにより、次にReadWriteToが呼び出された際に、残りのデータから読み取りが開始されます。
  7. n = int64(m): io.WriterToインターフェースの定義に従い、書き込まれたバイト数mint64型に変換して戻り値nに設定します。
  8. if m != len(b) && err == nil: もしw.Writeが、渡されたバイトスライスbの全てのバイトを書き込まなかった(mlen(b)より小さい)にもかかわらず、エラーを返さなかった場合、これは「短縮書き込み(short write)」と呼ばれる状況です。この場合、io.ErrShortWriteエラーを返します。これは、io.Writerの実装が部分的な書き込みを許容しつつも、エラーを返さない場合に、呼び出し元にその事実を伝えるためのGoの標準的な慣習です。

strings.ReaderWriteToメソッド

strings.ReaderWriteToメソッドは、基本的なロジックはbytes.Readerと同じですが、バイトスライスではなく文字列を扱う点が異なります。

  1. r.prevRune = -1: strings.Readerもルーンの読み取り状態を管理するため、prevRuneをリセットします。
  2. if r.i >= len(r.s): r.iが元の文字列r.sの長さを超えている場合、0バイトとio.EOFを返します。
  3. s := r.s[r.i:]: 現在の読み取り位置r.iから、文字列r.sの残りの部分を新しい文字列sとして取得します。
  4. m, err := io.WriteString(w, s): 取得した文字列sを、io.Writer wに書き込むためにio.WriteString関数を使用します。io.WriteStringは、io.Writerio.StringWriterインターフェースを実装している場合に、文字列をバイトスライスに変換するオーバーヘッドを避けて直接書き込む最適化パスを提供します。そうでない場合は、内部で[]byte(s)に変換してw.Writeを呼び出します。
  5. if m > len(s): io.WriteStringが、渡された文字列sのバイト長よりも多くのバイトを書き込んだと報告した場合、パニックを発生させます。
  6. r.i += m: 実際に書き込まれたバイト数mだけ、strings.Readerの内部読み取り位置r.iを進めます。
  7. n = int64(m): 書き込まれたバイト数mint64型に変換して戻り値nに設定します。
  8. if m != len(s) && err == nil: bytes.Readerと同様に、短縮書き込みが発生し、かつエラーが返されなかった場合にio.ErrShortWriteを返します。

これらの変更により、bytes.Readerstrings.Readerio.WriterToインターフェースを完全に実装し、io.Copyなどの関数がより効率的なデータ転送パスを利用できるようになりました。

関連リンク

参考にした情報源リンク