[インデックス 1171] ファイルの概要
このコミットは、Go言語の標準ライブラリio
パッケージ内のファイルコピー関連関数Copyn
のシグネチャ変更と、新しい関数Copy
の追加に関するものです。
コミット
commit 9dc4b1ca90c6fa3fc2d25c451f655712431f9dd8
Author: Russ Cox <rsc@golang.org>
Date: Tue Nov 18 18:45:51 2008 -0800
make Copyn take and return int64.
add Copy.
R=gri
DELTA=52 (37 added, 1 deleted, 14 changed)
OCL=19557
CL=19559
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/9dc4b1ca90c6fa3fc2d25c451f655712431f9dd8
元コミット内容
Copyn
関数の引数と戻り値の型をint
からint64
に変更し、Copy
関数を新しく追加しました。
変更の背景
この変更は、Go言語の初期開発段階におけるio
パッケージの設計改善の一環として行われました。
-
Copyn
の型変更 (int
->int64
):- 元の
Copyn
関数は、コピーするバイト数n
とコピーされたバイト数c
をint
型で扱っていました。 - しかし、ファイルやネットワークストリームなどのI/O操作では、非常に大きなデータを扱うことが一般的です。
int
型(32ビットシステムでは通常2GBまで)では表現できるバイト数に限りがあり、これを超えるサイズのデータをコピーしようとするとオーバーフローや予期せぬ動作を引き起こす可能性があります。 int64
型を使用することで、より大きなバイト数(約9エクサバイトまで)を安全に表現できるようになり、大規模なI/O操作に対応できるようになります。これは、Go言語が設計当初からスケーラビリティと堅牢性を重視していたことを示唆しています。
- 元の
-
Copy
関数の追加:Copyn
は指定されたバイト数だけコピーしますが、多くのI/Oシナリオでは、データの終端(EOF: End Of File)に達するまで全てのデータをコピーしたい場合があります。Copy
関数は、このような「EOFまで全てコピーする」という一般的なユースケースを簡潔に記述できるようにするために追加されました。これにより、開発者はCopyn
で非常に大きなn
を指定したり、ループでEOFをチェックしながらRead
/Write
を繰り返したりする手間を省くことができます。
これらの変更は、io
パッケージが提供するI/Oプリミティブの堅牢性と利便性を向上させ、Go言語が大規模なシステムやネットワークアプリケーションを効率的に扱うための基盤を強化するものです。
前提知識の解説
1. io.Reader
とio.Writer
インターフェース
Go言語のio
パッケージは、I/O操作の基本的なプリミティブを提供します。その中でも特に重要なのがReader
とWriter
インターフェースです。
-
io.Reader
: データを読み出すためのインターフェースです。type Reader interface { Read(p []byte) (n int, err error) }
Read
メソッドは、p
に最大len(p)
バイトを読み込み、読み込んだバイト数n
とエラーerr
を返します。データがこれ以上ない場合(EOF)、n
は0になり、err
はio.EOF
(またはErrEOF
、このコミット時点ではErrEOF
が使われている)を返します。 -
io.Writer
: データを書き込むためのインターフェースです。type Writer interface { Write(p []byte) (n int, err error) }
Write
メソッドは、p
からlen(p)
バイトを書き込み、書き込んだバイト数n
とエラーerr
を返します。
これらのインターフェースは、ファイル、ネットワーク接続、メモリバッファなど、様々なデータソースやシンクに対して統一的なI/O操作を可能にするGo言語の強力な抽象化メカニズムです。Copyn
やCopy
関数は、このReader
とWriter
インターフェースを引数に取ることで、具体的なI/Oデバイスに依存しない汎用的なコピー機能を提供します。
2. int
とint64
のデータ型
int
: Go言語のint
型は、実行環境のCPUアーキテクチャに依存する符号付き整数型です。32ビットシステムでは32ビット(-2,147,483,648 から 2,147,483,647)、64ビットシステムでは64ビット(-9,223,372,036,854,775,808 から 9,223,372,036,854,775,807)の範囲を表現します。int64
: Go言語のint64
型は、常に64ビット幅の符号付き整数型です。これにより、システムアーキテクチャに関わらず、非常に大きな数値を一貫して表現できます。I/O操作でバイト数を扱う場合、ファイルサイズや転送データ量がint
の最大値を超える可能性があるため、int64
がより安全で適切な選択となります。
3. EOF (End Of File)
EOFは、データストリームの終端を示す概念です。ファイルやネットワーク接続からデータを読み込む際、これ以上読み込むデータがない状態を指します。Go言語のio.Reader
インターフェースでは、Read
メソッドがn=0
かつerr=io.EOF
(またはErrEOF
)を返すことでEOFを通知します。
技術的詳細
このコミットは、src/lib/io.go
ファイルに対して行われました。
Copyn
関数の変更点
元のCopyn
関数は、指定されたバイト数n
をsrc
からdst
へコピーするものでした。変更点は以下の通りです。
-
シグネチャの変更:
- 変更前:
export func Copyn(src Read, dst Write, n int) (c int, err *os.Error)
- 変更後:
export func Copyn(src Read, dst Write, n int64) (written int64, err *os.Error)
- コピーするバイト数
n
と、実際に書き込まれたバイト数written
の型がint
からint64
に変更されました。これにより、より大きなサイズのデータを扱うことが可能になります。
- 変更前:
-
内部ロジックの調整:
written
変数がint64
型で宣言され、コピーされたバイト数の合計を保持します。- ループ条件が
written < n
となり、int64
同士の比較が行われます。 - バッファサイズ
len(buf)
はint
型であるため、n - written
の結果がint64
であることから、バッファに読み込むバイト数l
を決定する際にint(n - written)
のように型変換が行われています。これは、l
がバッファの長さを超えないように、かつint
型で表現できる範囲に収まるようにするためです。 src.Read
から読み込んだバイト数nr
とdst.Write
に書き込んだバイト数nw
はint
型ですが、written
に加算する際にint64(nw)
のように型変換が行われます。nr != nw
の場合にos.EIO
エラーを返すロジックが追加されました。これは、読み込んだバイト数と書き込んだバイト数が一致しない場合にI/Oエラーとして扱うことで、データの一貫性を保証します。src.Read
がnr == 0
を返した場合(EOFに達した場合)に、ErrEOF
を返すように変更されました。これにより、指定されたバイト数に満たないうちにソースが終了した場合でも、その状態を正確に呼び出し元に伝えることができます。
Copy
関数の追加
新しく追加されたCopy
関数は、src
からdst
へEOFに達するまで全てのデータをコピーします。
-
シグネチャ:
export func Copy(src Read, dst Write) (written int64, err *os.Error)
Copyn
と同様に、書き込まれたバイト数written
はint64
型で返されます。- コピーするバイト数を指定する引数
n
はありません。
-
内部ロジック:
Copyn
と同様に32KBのバッファを使用します。- 無限ループ
for {}
の中でsrc.Read(buf)
とdst.Write(buf[0:nr])
を繰り返します。 src.Read
がエラーを返した場合、またはnr == 0
(EOF)を返した場合にループを終了します。nr != nw
の場合にos.EIO
エラーを返すロジックも含まれており、データの一貫性を保証します。
コアとなるコードの変更箇所
src/lib/io.go
ファイルにおけるCopyn
関数とCopy
関数の定義部分です。
--- a/src/lib/io.go
+++ b/src/lib/io.go
@@ -40,7 +40,7 @@ export func WriteString(w Write, s string) (n int, err *os.Error) {
return r, e
}
-// Read until buffer is full, EOF, or error
+// Read until buffer is full, EOF, or error
export func Readn(fd Read, buf *[]byte) (n int, err *os.Error) {
n = 0;
for n < len(buf) {
@@ -79,34 +79,70 @@ export func MakeFullReader(fd Read) Read {
// Copies n bytes (or until EOF is reached) from src to dst.
// Returns the number of bytes copied and the error, if any.
-export func Copyn(src Read, dst Write, n int) (c int, err *os.Error) {
- buf := new([]byte, 32*1024); // BUG 6g crashes on non-pointer array slices
- c = 0;
- for c < n {
- l := n - c;
- if l > len(buf) {
- l = len(buf)
+export func Copyn(src Read, dst Write, n int64) (written int64, err *os.Error) {
+ buf := new([]byte, 32*1024);
+ for written < n {
+ var l int;
+ if n - written > int64(len(buf)) {
+ l = len(buf);
+ } else {
+ l = int(n - written);
}
nr, er := src.Read(buf[0 : l]);
if nr > 0 {
nw, ew := dst.Write(buf[0 : nr]);
- if nw != nr || ew != nil {
- c += nw;
- if ew == nil {
- ew = os.EIO
- }
+ if nw > 0 {
+ written += int64(nw);
+ }
+ if ew != nil {
err = ew;
break;
}
- c += nr;
+ if nr != nw {
+ err = os.EIO;
+ break;
+ }
}
if er != nil {
err = er;
break;
}
if nr == 0 {
+ err = ErrEOF;
break;
}
}
- return c, err
+ return written, err
}
+
+// Copies from src to dst until EOF is reached.
+// Returns the number of bytes copied and the error, if any.
+export func Copy(src Read, dst Write) (written int64, err *os.Error) {
+ buf := new([]byte, 32*1024);
+ for {
+ nr, er := src.Read(buf);
+ if nr > 0 {
+ nw, ew := dst.Write(buf[0:nr]);
+ if nw > 0 {
+ written += int64(nw);
+ }
+ if ew != nil {
+ err = ew;
+ break;
+ }
+ if nr != nw {
+ err = os.EIO;
+ break;
+ }
+ }
+ if er != nil {
+ err = er;
+ break;
+ }
+ if nr == 0 {
+ break;
+ }
+ }
+ return written, err
+}
+
コアとなるコードの解説
Copyn
関数の変更点詳細
-
n
とwritten
の型をint64
へ変更:export func Copyn(src Read, dst Write, n int64) (written int64, err *os.Error)
- これにより、コピー可能なデータサイズの上限が大幅に引き上げられ、大規模なファイルやストリームのコピーに対応できるようになりました。
-
バッファリングとコピーロジック:
buf := new([]byte, 32*1024)
: 32KBのバッファを使用します。これは、I/O操作の効率を高めるための一般的なプラクティスです。for written < n
: 指定されたバイト数n
に達するまでループを続けます。l = int(n - written)
: 残りのコピーバイト数n - written
がバッファサイズlen(buf)
より小さい場合、その値をl
に設定します。これにより、必要以上に大きなバッファを読み込もうとすることを防ぎます。int64
からint
へのキャストは、l
がバッファのスライスインデックスとして使用されるため必要です。nr, er := src.Read(buf[0 : l])
:src
から最大l
バイトを読み込みます。nw, ew := dst.Write(buf[0 : nr])
: 読み込んだnr
バイトをdst
に書き込みます。written += int64(nw)
: 実際に書き込まれたバイト数nw
をwritten
に加算します。ここでもint64
へのキャストが行われます。- エラーハンドリングの強化:
if ew != nil
: 書き込みエラーが発生した場合、そのエラーを返し、ループを終了します。if nr != nw
: 読み込んだバイト数と書き込んだバイト数が一致しない場合、os.EIO
(I/Oエラー)を返してループを終了します。これは、部分的な書き込みが発生した場合のデータ破損を防ぐための重要なチェックです。if er != nil
: 読み込みエラーが発生した場合、そのエラーを返し、ループを終了します。if nr == 0
:src.Read
が0バイトを返した場合、これはEOFに達したことを意味します。この場合、ErrEOF
をエラーとして返し、ループを終了します。これにより、指定されたバイト数に満たないうちにソースが終了したことを明示的に通知します。
Copy
関数の追加詳細
-
シグネチャ:
export func Copy(src Read, dst Write) (written int64, err *os.Error)
Copyn
と同様に、コピーされたバイト数をint64
で返します。
-
EOFまでのコピーロジック:
for {}
: 無限ループを使用し、明示的な終了条件が満たされるまでコピーを続けます。nr, er := src.Read(buf)
:src
からバッファが満杯になるまで読み込みます。nw, ew := dst.Write(buf[0:nr])
: 読み込んだバイトをdst
に書き込みます。- エラーハンドリングと終了条件:
if ew != nil
: 書き込みエラーが発生した場合、ループを終了します。if nr != nw
: 読み書きバイト数不一致の場合、os.EIO
を返してループを終了します。if er != nil
: 読み込みエラーが発生した場合、ループを終了します。if nr == 0
:src.Read
が0バイトを返した場合、これはEOFに達したことを意味します。この場合、エラーは返さずにループを終了します。Copy
関数はEOFまでコピーすることを目的としているため、EOF自体はエラーとは見なされません。
これらの変更により、Go言語のio
パッケージは、より堅牢で柔軟なデータコピー機能を提供し、様々なI/Oシナリオに対応できるようになりました。特にint64
の導入は、大規模データ処理におけるGoの適応性を示す重要なステップです。
関連リンク
- Go言語の
io
パッケージに関する公式ドキュメント(現在のバージョン): https://pkg.go.dev/io - Go言語の
int
型とint64
型に関する公式ドキュメント: https://go.dev/ref/spec#Numeric_types
参考にした情報源リンク
- Go言語のGitHubリポジトリ: https://github.com/golang/go
- Go言語の初期コミット履歴 (このコミットが含まれる): https://github.com/golang/go/commits/master?after=9dc4b1ca90c6fa3fc2d25c451f655712431f9dd8+1
- Go言語の
os
パッケージ(os.Error
やos.EIO
の文脈): https://pkg.go.dev/os (現在のバージョン) - Go言語の
io.EOF
に関する情報: https://pkg.go.dev/io#pkg-variables (現在のバージョン) - Go言語の
Read
とWrite
インターフェースに関する情報: https://pkg.go.dev/io#Reader (現在のバージョン) - Go言語の
Copy
関数に関する情報: https://pkg.go.dev/io#Copy (現在のバージョン) - Go言語の
Copyn
関数に関する情報: https://pkg.go.dev/io#CopyN (現在のバージョン)- 注: このコミット時点では
Copyn
という名前ですが、現在のGo言語ではCopyN
という名前になっています。機能はほぼ同じです。
- 注: このコミット時点では