[インデックス 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.Readerとstrings.ReaderにWriteToメソッドを追加する」に対応するものです。
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.ReaderFromとio.WriterToインターフェースの存在をチェックします。もしio.Readerがio.WriterToを実装している場合、io.CopyはそのWriteToメソッドを呼び出すことで、より効率的なデータ転送を実現できます。これは、io.Copyが内部でバッファリングを行う必要がなくなり、データソース自身が最適な方法でデータを書き込み先に転送できるためです。
bytes.Readerとstrings.Readerは、それぞれバイトスライスと文字列からデータを読み出すためのio.Readerの実装です。これらの型がio.WriterToを実装していなかったため、io.Copyを使用した場合に、内部でバッファリングを伴う一般的なRead/Writeループが実行されていました。これは、特に大きなデータの場合にパフォーマンスのボトルネックとなる可能性がありました。
Issue #4031は、この非効率性を解消し、bytes.Readerとstrings.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.Readerとstrings.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は、内部でsrcがio.WriterToを実装しているか、またはdstがio.ReaderFromを実装しているかをチェックします。もしsrcがio.WriterToを実装していれば、src.WriteTo(dst)を呼び出します。これにより、src自身が最も効率的な方法でデータをdstに転送できます。この最適化パスは、特に大きなデータを扱う場合に、中間バッファリングのオーバーヘッドを削減し、パフォーマンスを向上させます。
技術的詳細
このコミットでは、bytes.Readerとstrings.ReaderのそれぞれにWriteToメソッドが追加されています。実装は両者で非常に似ていますが、bytes.Readerはバイトスライスを直接扱い、strings.Readerは文字列を扱うため、細部が異なります。
bytes.ReaderのWriteTo実装
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.Readerはio.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.Writerwに直接書き込みます。これがWriteToの核心であり、効率的なデータ転送を実現します。if m > len(b):w.Writeが、渡されたバイトスライスbの長さよりも多くのバイトを書き込んだと報告した場合、これは異常な状態であるためパニックを起こします。r.i += m: 実際に書き込まれたバイト数mだけ、Readerの内部ポインタr.iを進めます。n = int64(m):io.WriterToインターフェースの戻り値の型に合わせて、mをint64にキャストしてnに代入します。if m != len(b) && err == nil:w.Writeが、渡されたすべてのバイトを書き込まなかったにもかかわらず、エラーを返さなかった場合、これは「短縮書き込み(short write)」と呼ばれる状態です。この場合、io.ErrShortWriteエラーを返します。これは、io.Writerの実装が部分的な書き込みを許容しつつも、エラーを返さない場合に、呼び出し元にその事実を伝えるための標準的なGoの慣習です。
strings.ReaderのWriteTo実装
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.ReaderのWriteToは、bytes.Readerとほぼ同じロジックですが、バイトスライスではなく文字列を扱うため、w.Write(b)の代わりにio.WriteString(w, s)を使用しています。io.WriteStringは、io.Writerがio.StringWriterインターフェースを実装している場合に最適化されたパスを提供し、そうでない場合はw.Write([]byte(s))にフォールバックします。これにより、文字列のコピーを最小限に抑えつつ、効率的な書き込みを実現します。
テストの追加
このコミットでは、bytes/reader_test.goとstrings/reader_test.goにそれぞれTestReaderWriteToとTestWriteToというテスト関数が追加されています。これらのテストは、WriteToメソッドが期待通りに動作し、正確なバイト数を返し、エラー処理が適切に行われることを確認します。特に、io.EOFやio.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.Readerとstrings.Readerの型定義コメントにio.WriterToが追加されたことと、それぞれの型にWriteToメソッドが実装されたことを示しています。
bytes.ReaderのWriteToメソッド
r.prevRune = -1:bytes.Readerはルーン(Unicodeコードポイント)を読み取るための内部状態prevRuneを持っています。WriteToはバイト単位でデータを処理するため、ルーンの読み取り状態をリセットします。if r.i >= len(r.s):r.iは現在の読み取り位置を示します。もし読み取り位置が元のバイトスライスr.sの長さを超えている場合、読み取るべきデータは残っていません。この場合、0バイトを書き込み、io.EOF(End Of File)エラーを返します。これは、データソースの終端に達したことを示す標準的な方法です。b := r.s[r.i:]: 現在の読み取り位置r.iから、バイトスライスr.sの残りの部分を新しいスライスbとして取得します。このスライスbが、io.Writerに書き込まれるデータ本体となります。m, err := w.Write(b): 取得したバイトスライスbを、引数として渡されたio.WriterインターフェースwのWriteメソッドに渡して書き込みを実行します。mは実際に書き込まれたバイト数、errは書き込み中に発生したエラーです。if m > len(b):w.Writeが、渡されたバイトスライスbの長さよりも多くのバイトを書き込んだと報告した場合、これは論理的にありえない異常な状態です。このような場合、プログラムの整合性を保つためにパニック(実行時エラー)を発生させます。r.i += m: 実際にwに書き込まれたバイト数mだけ、bytes.Readerの内部読み取り位置r.iを進めます。これにより、次にReadやWriteToが呼び出された際に、残りのデータから読み取りが開始されます。n = int64(m):io.WriterToインターフェースの定義に従い、書き込まれたバイト数mをint64型に変換して戻り値nに設定します。if m != len(b) && err == nil: もしw.Writeが、渡されたバイトスライスbの全てのバイトを書き込まなかった(mがlen(b)より小さい)にもかかわらず、エラーを返さなかった場合、これは「短縮書き込み(short write)」と呼ばれる状況です。この場合、io.ErrShortWriteエラーを返します。これは、io.Writerの実装が部分的な書き込みを許容しつつも、エラーを返さない場合に、呼び出し元にその事実を伝えるためのGoの標準的な慣習です。
strings.ReaderのWriteToメソッド
strings.ReaderのWriteToメソッドは、基本的なロジックはbytes.Readerと同じですが、バイトスライスではなく文字列を扱う点が異なります。
r.prevRune = -1:strings.Readerもルーンの読み取り状態を管理するため、prevRuneをリセットします。if r.i >= len(r.s):r.iが元の文字列r.sの長さを超えている場合、0バイトとio.EOFを返します。s := r.s[r.i:]: 現在の読み取り位置r.iから、文字列r.sの残りの部分を新しい文字列sとして取得します。m, err := io.WriteString(w, s): 取得した文字列sを、io.Writerwに書き込むためにio.WriteString関数を使用します。io.WriteStringは、io.Writerがio.StringWriterインターフェースを実装している場合に、文字列をバイトスライスに変換するオーバーヘッドを避けて直接書き込む最適化パスを提供します。そうでない場合は、内部で[]byte(s)に変換してw.Writeを呼び出します。if m > len(s):io.WriteStringが、渡された文字列sのバイト長よりも多くのバイトを書き込んだと報告した場合、パニックを発生させます。r.i += m: 実際に書き込まれたバイト数mだけ、strings.Readerの内部読み取り位置r.iを進めます。n = int64(m): 書き込まれたバイト数mをint64型に変換して戻り値nに設定します。if m != len(s) && err == nil:bytes.Readerと同様に、短縮書き込みが発生し、かつエラーが返されなかった場合にio.ErrShortWriteを返します。
これらの変更により、bytes.Readerとstrings.Readerはio.WriterToインターフェースを完全に実装し、io.Copyなどの関数がより効率的なデータ転送パスを利用できるようになりました。
関連リンク
- Go Issue #4031: https://github.com/golang/go/issues/4031
- Go CL 6632046: https://golang.org/cl/6632046
参考にした情報源リンク
- Go言語
ioパッケージドキュメント: https://pkg.go.dev/io - Go言語
bytesパッケージドキュメント: https://pkg.go.dev/bytes - Go言語
stringsパッケージドキュメント: https://pkg.go.dev/strings - Go言語
io.Copyの実装 (Goソースコード): https://cs.opensource.google/go/go/+/refs/tags/go1.22.4:src/io/io.go;l387 - Go言語
io.WriteStringの実装 (Goソースコード): https://cs.opensource.google/go/go/+/refs/tags/go1.22.4:src/io/io.go;l409