[インデックス 11914] ファイルの概要
このコミットは、Go言語の標準ライブラリにおいて、bytes.Reader
および strings.Reader
型が io.ReaderAt
インターフェースを実装するように変更するものです。これにより、これらのリーダーがバイトスライスや文字列から特定のオフセット位置からデータを読み込む機能を提供し、既存のカスタム実装を置き換えることでコードの重複を排除し、一貫性を向上させます。
コミット
commit 7127b6fddcc07b960452aaba5dbbe84001e2e547
Author: Brad Fitzpatrick <bradfitz@golang.org>
Date: Wed Feb 15 12:58:00 2012 +1100
bytes,strings: make *Reader implement io.ReaderAt
R=golang-dev, adg, bradfitz, r
CC=golang-dev
https://golang.org/cl/5675053
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/7127b6fddcc07b960452aaba5dbbe84001e2e547
元コミット内容
bytes,strings: make *Reader implement io.ReaderAt
このコミットの目的は、bytes
パッケージのReader
とstrings
パッケージのReader
がio.ReaderAt
インターフェースを実装するようにすることです。
変更の背景
Go言語の標準ライブラリには、バイトスライスや文字列を読み込むためのbytes.Reader
とstrings.Reader
が存在します。これらは既にio.Reader
、io.Seeker
、io.ByteScanner
、io.RuneScanner
といったインターフェースを実装していました。しかし、特定のオフセットからデータを読み込む機能を提供するio.ReaderAt
インターフェースは実装していませんでした。
archive/zip
やmime/multipart
といった他のパッケージでは、ファイルやデータの一部をランダムアクセスで読み込む必要があり、そのためにsliceReaderAt
やstringReaderAt
といったカスタムのio.ReaderAt
実装が独自に定義されていました。これらのカスタム実装は、bytes.Reader
やstrings.Reader
が提供する基本的な機能と重複しており、コードの冗長性やメンテナンスの複雑さを引き起こしていました。
このコミットの背景には、以下の目的があります。
- コードの重複排除と一貫性の向上:
bytes.Reader
とstrings.Reader
にio.ReaderAt
を実装することで、他のパッケージで独自に定義されていた同様の機能を持つ型を廃止し、標準ライブラリ全体でのコードの一貫性と再利用性を高めます。 - 機能の拡充:
bytes.Reader
とstrings.Reader
がより多くのio
インターフェースをサポートすることで、これらの型がより汎用的に利用できるようになります。特に、io.NewSectionReader
のような関数はio.ReaderAt
を引数に取るため、この変更によりbytes.Reader
やstrings.Reader
を直接渡せるようになり、コードが簡潔になります。 - パフォーマンスの最適化:
bytes.Reader
とstrings.Reader
は内部的にバイトスライスや文字列を直接参照しているため、ReadAt
の実装は効率的に行えます。これにより、カスタム実装と比較して潜在的なパフォーマンス上の利点も期待できます。
前提知識の解説
このコミットを理解するためには、以下のGo言語の基本的な概念と標準ライブラリのインターフェースに関する知識が必要です。
1. io.Reader
インターフェース
io.Reader
はGo言語で最も基本的なI/Oインターフェースの一つです。
type Reader interface {
Read(p []byte) (n int, err error)
}
Read
メソッドは、データをp
に読み込み、読み込んだバイト数n
とエラーerr
を返します。データストリームから順次読み込むためのインターフェースです。
2. io.Seeker
インターフェース
io.Seeker
は、データストリーム内の読み書き位置を変更するためのインターフェースです。
type Seeker interface {
Seek(offset int64, whence int) (int64, error)
}
Seek
メソッドは、offset
とwhence
(io.SeekStart
, io.SeekCurrent
, io.SeekEnd
のいずれか)に基づいて読み書き位置を設定し、新しいオフセットを返します。
3. io.ReaderAt
インターフェース
io.ReaderAt
は、データストリーム内の任意のオフセットからデータを読み込むためのインターフェースです。
type ReaderAt interface {
ReadAt(p []byte, off int64) (n int, err error)
}
ReadAt
メソッドは、off
で指定されたオフセットからp
にデータを読み込みます。Read
とは異なり、ReadAt
は内部の読み書き位置(シークポインタ)を変更しません。これは、複数のゴルーチンが同時に同じReaderAt
から読み込む場合に特に有用です。
4. bytes.Reader
と strings.Reader
bytes.Reader
: バイトスライス ([]byte
) からデータを読み込むためのリーダーです。bytes.NewReader([]byte)
で作成されます。strings.Reader
: 文字列 (string
) からデータを読み込むためのリーダーです。strings.NewReader(string)
で作成されます。
これらは、メモリ上のデータをファイルのように扱うための便利な型です。
5. io.NewSectionReader
io.NewSectionReader
は、既存のio.ReaderAt
から特定のセクション(範囲)を読み込むためのio.SectionReader
を返します。
func NewSectionReader(r ReaderAt, off int64, n int64) *SectionReader
これは、大きなデータソースの一部だけをio.Reader
として扱いたい場合に非常に便利です。
技術的詳細
このコミットの主要な技術的変更は、bytes.Reader
とstrings.Reader
の構造体にReadAt
メソッドを追加し、それらがio.ReaderAt
インターフェースを満たすようにすることです。
bytes.Reader
の変更 (src/pkg/bytes/reader.go
)
bytes.Reader
の定義が更新され、io.ReaderAt
インターフェースを実装することが明示されます。
// A Reader implements the io.Reader, io.ReaderAt, 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.
type Reader struct {
s []byte
i int // current reading index
prevRune int // index of previous rune; or < 0
}
そして、ReadAt
メソッドが追加されます。
func (r *Reader) ReadAt(b []byte, off int64) (n int, err error) {
if off < 0 {
return 0, errors.New("bytes: invalid offset")
}
if off >= int64(len(r.s)) {
return 0, io.EOF
}
n = copy(b, r.s[int(off):])
if n < len(b) {
err = io.EOF
}
return
}
- オフセットの検証:
off < 0
の場合、無効なオフセットとしてエラーを返します。 - EOFの処理:
off
が元のバイトスライスの長さを超える場合、io.EOF
を返します。これは、読み取り開始位置がデータの終端を超えていることを意味します。 - データのコピー:
copy(b, r.s[int(off):])
を使用して、指定されたオフセットoff
からバイトスライスr.s
の残りの部分をb
にコピーします。copy
関数は、コピーされたバイト数を返します。 - 部分的な読み取りとEOF:
n < len(b)
の場合、要求されたバイト数len(b)
よりも少ないバイトしか読み込めなかったことを意味します。これは、データの終端に達したためであり、io.EOF
をエラーとして設定します。
strings.Reader
の変更 (src/pkg/strings/reader.go
)
strings.Reader
も同様に、io.ReaderAt
インターフェースを実装することが明示され、ReadAt
メソッドが追加されます。実装ロジックはbytes.Reader
とほぼ同じですが、対象が文字列である点が異なります。
// A Reader implements the io.Reader, io.ReaderAt, io.Seeker,
// io.ByteScanner, and io.RuneScanner interfaces by reading
// from a string.
type Reader struct {
s string
i int // current reading index
prevRune int // index of previous rune; or < 0
}
ReadAt
メソッドの実装もbytes.Reader
とパラレルです。
func (r *Reader) ReadAt(b []byte, off int64) (n int, err error) {
if off < 0 {
return 0, errors.New("strings: invalid offset")
}
if off >= int64(len(r.s)) {
return 0, io.EOF
}
n = copy(b, r.s[int(off):])
if n < len(b) {
err = io.EOF
}
return
}
既存コードの置き換え
この変更に伴い、archive/zip
およびmime/multipart
パッケージ内のカスタムio.ReaderAt
実装(sliceReaderAt
やstringReaderAt
)が削除され、代わりにbytes.NewReader
やstrings.NewReader
が使用されるようになります。
src/pkg/archive/zip/reader_test.go
:sliceReaderAt
の代わりにbytes.NewReader
を使用。src/pkg/archive/zip/writer_test.go
:sliceReaderAt
の代わりにbytes.NewReader
を使用。src/pkg/archive/zip/zip_test.go
:stringReaderAt
の代わりにstrings.NewReader
を使用。src/pkg/mime/multipart/formdata.go
:sliceReaderAt
の代わりにbytes.NewReader
を使用。
これにより、コードの重複が解消され、標準ライブラリの型をより有効活用できるようになります。
テストの追加
bytes/reader_test.go
とstrings/reader_test.go
に、新しく追加されたReadAt
メソッドの動作を検証するためのテストケースが追加されています。これらのテストは、様々なオフセットと読み取りサイズでのReadAt
の挙動、特にエラーケース(無効なオフセット、EOF)を網羅しています。
コアとなるコードの変更箇所
src/pkg/bytes/reader.go
--- a/src/pkg/bytes/reader.go
+++ b/src/pkg/bytes/reader.go
@@ -10,8 +10,9 @@ import (
"unicode/utf8"
)
-// A Reader implements the io.Reader, io.Seeker, io.ByteScanner, and
-// io.RuneScanner interfaces by reading from a byte slice.
+// A Reader implements the io.Reader, io.ReaderAt, 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.
type Reader struct {
s []byte
@@ -41,6 +42,20 @@ func (r *Reader) Read(b []byte) (n int, err error) {
return
}
+func (r *Reader) ReadAt(b []byte, off int64) (n int, err error) {
+ if off < 0 {
+ return 0, errors.New("bytes: invalid offset")
+ }
+ if off >= int64(len(r.s)) {
+ return 0, io.EOF
+ }
+ n = copy(b, r.s[int(off):])
+ if n < len(b) {
+ err = io.EOF
+ }
+ return
+}
+
func (r *Reader) ReadByte() (b byte, err error) {
if r.i >= len(r.s) {
return 0, io.EOF
src/pkg/strings/reader.go
--- a/src/pkg/strings/reader.go
+++ b/src/pkg/strings/reader.go
@@ -10,8 +10,9 @@ import (
"unicode/utf8"
)
-// A Reader implements the io.Reader, io.Seeker, io.ByteScanner, and
-// io.RuneScanner interfaces by reading from a string.
+// A Reader implements the io.Reader, io.ReaderAt, io.Seeker,
+// io.ByteScanner, and io.RuneScanner interfaces by reading
+// from a string.
type Reader struct {
s string
i int // current reading index
@@ -40,6 +41,20 @@ func (r *Reader) Read(b []byte) (n int, err error) {
return
}
+func (r *Reader) ReadAt(b []byte, off int64) (n int, err error) {
+ if off < 0 {
+ return 0, errors.New("strings: invalid offset")
+ }
+ if off >= int64(len(r.s)) {
+ return 0, io.EOF
+ }
+ n = copy(b, r.s[int(off):])
+ if n < len(b) {
+ err = io.EOF
+ }
+ return
+}
+
func (r *Reader) ReadByte() (b byte, err error) {
if r.i >= len(r.s) {
return 0, io.EOF
コアとなるコードの解説
上記の差分が示すように、bytes.Reader
とstrings.Reader
のそれぞれの型定義のコメントが更新され、io.ReaderAt
インターフェースを実装することが明示されています。
最も重要な変更は、ReadAt
メソッドの追加です。
ReadAt(b []byte, off int64) (n int, err error)
:b []byte
: 読み込んだデータを格納するバイトスライス。off int64
: 読み取りを開始するオフセット(バイト単位)。n int
: 実際に読み込んだバイト数。err error
: 発生したエラー(例:io.EOF
、無効なオフセット)。
このメソッドの実装は非常にシンプルかつ効率的です。内部のバイトスライス(bytes.Reader
の場合はr.s []byte
、strings.Reader
の場合はr.s string
)から、指定されたoff
から始まる部分をb
に直接コピーします。
- オフセットのチェック:
off < 0
の場合、負のオフセットは無効であるため、errors.New("bytes: invalid offset")
またはerrors.New("strings: invalid offset")
を返します。 - EOFのチェック:
off
が元のデータの長さを超えている場合、読み取るべきデータがないため、io.EOF
を返します。 - データのコピー:
copy(b, r.s[int(off):])
が実際のデータコピーを行います。Goのcopy
関数は、ソーススライスの残りの部分をデスティネーションスライスにコピーし、コピーされたバイト数を返します。 - 部分的な読み取りとEOF:
n < len(b)
の場合、これは要求されたlen(b)
バイトをすべて読み込めなかったことを意味します。これは通常、データの終端に達したためであり、この場合err
にio.EOF
を設定します。これにより、呼び出し元はデータの終端に達したことを適切に処理できます。
この実装は、bytes.Reader
とstrings.Reader
が内部的に連続したメモリ領域(バイトスライスまたは文字列)を保持しているという特性を最大限に活用しており、非常に高速なランダムアクセス読み取りを提供します。
関連リンク
- Go言語の
io
パッケージのドキュメント: https://pkg.go.dev/io - Go言語の
bytes
パッケージのドキュメント: https://pkg.go.dev/bytes - Go言語の
strings
パッケージのドキュメント: https://pkg.go.dev/strings - Go言語の
archive/zip
パッケージのドキュメント: https://pkg.go.dev/archive/zip - Go言語の
mime/multipart
パッケージのドキュメント: https://pkg.go.dev/mime/multipart
参考にした情報源リンク
- Go言語の公式ドキュメント
- Go言語のソースコード
- Go言語の
io
インターフェースに関する一般的な解説記事