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

[インデックス 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パッケージのReaderstringsパッケージのReaderio.ReaderAtインターフェースを実装するようにすることです。

変更の背景

Go言語の標準ライブラリには、バイトスライスや文字列を読み込むためのbytes.Readerstrings.Readerが存在します。これらは既にio.Readerio.Seekerio.ByteScannerio.RuneScannerといったインターフェースを実装していました。しかし、特定のオフセットからデータを読み込む機能を提供するio.ReaderAtインターフェースは実装していませんでした。

archive/zipmime/multipartといった他のパッケージでは、ファイルやデータの一部をランダムアクセスで読み込む必要があり、そのためにsliceReaderAtstringReaderAtといったカスタムのio.ReaderAt実装が独自に定義されていました。これらのカスタム実装は、bytes.Readerstrings.Readerが提供する基本的な機能と重複しており、コードの冗長性やメンテナンスの複雑さを引き起こしていました。

このコミットの背景には、以下の目的があります。

  1. コードの重複排除と一貫性の向上: bytes.Readerstrings.Readerio.ReaderAtを実装することで、他のパッケージで独自に定義されていた同様の機能を持つ型を廃止し、標準ライブラリ全体でのコードの一貫性と再利用性を高めます。
  2. 機能の拡充: bytes.Readerstrings.Readerがより多くのioインターフェースをサポートすることで、これらの型がより汎用的に利用できるようになります。特に、io.NewSectionReaderのような関数はio.ReaderAtを引数に取るため、この変更によりbytes.Readerstrings.Readerを直接渡せるようになり、コードが簡潔になります。
  3. パフォーマンスの最適化: bytes.Readerstrings.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メソッドは、offsetwhenceio.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.Readerstrings.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.Readerstrings.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実装(sliceReaderAtstringReaderAt)が削除され、代わりにbytes.NewReaderstrings.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.gostrings/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.Readerstrings.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 []bytestrings.Readerの場合はr.s string)から、指定されたoffから始まる部分をbに直接コピーします。

  1. オフセットのチェック: off < 0 の場合、負のオフセットは無効であるため、errors.New("bytes: invalid offset") または errors.New("strings: invalid offset") を返します。
  2. EOFのチェック: offが元のデータの長さを超えている場合、読み取るべきデータがないため、io.EOFを返します。
  3. データのコピー: copy(b, r.s[int(off):]) が実際のデータコピーを行います。Goのcopy関数は、ソーススライスの残りの部分をデスティネーションスライスにコピーし、コピーされたバイト数を返します。
  4. 部分的な読み取りとEOF: n < len(b) の場合、これは要求されたlen(b)バイトをすべて読み込めなかったことを意味します。これは通常、データの終端に達したためであり、この場合errio.EOFを設定します。これにより、呼び出し元はデータの終端に達したことを適切に処理できます。

この実装は、bytes.Readerstrings.Readerが内部的に連続したメモリ領域(バイトスライスまたは文字列)を保持しているという特性を最大限に活用しており、非常に高速なランダムアクセス読み取りを提供します。

関連リンク

参考にした情報源リンク

  • Go言語の公式ドキュメント
  • Go言語のソースコード
  • Go言語のioインターフェースに関する一般的な解説記事