[インデックス 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.Writer
w
に直接書き込みます。これが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.Writer
w
に書き込むために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