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

[インデックス 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パッケージの設計改善の一環として行われました。

  1. Copynの型変更 (int -> int64):

    • 元のCopyn関数は、コピーするバイト数nとコピーされたバイト数cint型で扱っていました。
    • しかし、ファイルやネットワークストリームなどのI/O操作では、非常に大きなデータを扱うことが一般的です。int型(32ビットシステムでは通常2GBまで)では表現できるバイト数に限りがあり、これを超えるサイズのデータをコピーしようとするとオーバーフローや予期せぬ動作を引き起こす可能性があります。
    • int64型を使用することで、より大きなバイト数(約9エクサバイトまで)を安全に表現できるようになり、大規模なI/O操作に対応できるようになります。これは、Go言語が設計当初からスケーラビリティと堅牢性を重視していたことを示唆しています。
  2. Copy関数の追加:

    • Copynは指定されたバイト数だけコピーしますが、多くのI/Oシナリオでは、データの終端(EOF: End Of File)に達するまで全てのデータをコピーしたい場合があります。
    • Copy関数は、このような「EOFまで全てコピーする」という一般的なユースケースを簡潔に記述できるようにするために追加されました。これにより、開発者はCopynで非常に大きなnを指定したり、ループでEOFをチェックしながらRead/Writeを繰り返したりする手間を省くことができます。

これらの変更は、ioパッケージが提供するI/Oプリミティブの堅牢性と利便性を向上させ、Go言語が大規模なシステムやネットワークアプリケーションを効率的に扱うための基盤を強化するものです。

前提知識の解説

1. io.Readerio.Writerインターフェース

Go言語のioパッケージは、I/O操作の基本的なプリミティブを提供します。その中でも特に重要なのがReaderWriterインターフェースです。

  • io.Reader: データを読み出すためのインターフェースです。

    type Reader interface {
        Read(p []byte) (n int, err error)
    }
    

    Readメソッドは、pに最大len(p)バイトを読み込み、読み込んだバイト数nとエラーerrを返します。データがこれ以上ない場合(EOF)、nは0になり、errio.EOF(またはErrEOF、このコミット時点ではErrEOFが使われている)を返します。

  • io.Writer: データを書き込むためのインターフェースです。

    type Writer interface {
        Write(p []byte) (n int, err error)
    }
    

    Writeメソッドは、pからlen(p)バイトを書き込み、書き込んだバイト数nとエラーerrを返します。

これらのインターフェースは、ファイル、ネットワーク接続、メモリバッファなど、様々なデータソースやシンクに対して統一的なI/O操作を可能にするGo言語の強力な抽象化メカニズムです。CopynCopy関数は、このReaderWriterインターフェースを引数に取ることで、具体的なI/Oデバイスに依存しない汎用的なコピー機能を提供します。

2. intint64のデータ型

  • 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関数は、指定されたバイト数nsrcから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から読み込んだバイト数nrdst.Writeに書き込んだバイト数nwint型ですが、writtenに加算する際にint64(nw)のように型変換が行われます。
    • nr != nwの場合にos.EIOエラーを返すロジックが追加されました。これは、読み込んだバイト数と書き込んだバイト数が一致しない場合にI/Oエラーとして扱うことで、データの一貫性を保証します。
    • src.Readnr == 0を返した場合(EOFに達した場合)に、ErrEOFを返すように変更されました。これにより、指定されたバイト数に満たないうちにソースが終了した場合でも、その状態を正確に呼び出し元に伝えることができます。

Copy関数の追加

新しく追加されたCopy関数は、srcからdstへEOFに達するまで全てのデータをコピーします。

  • シグネチャ: export func Copy(src Read, dst Write) (written int64, err *os.Error)

    • Copynと同様に、書き込まれたバイト数writtenint64型で返されます。
    • コピーするバイト数を指定する引数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関数の変更点詳細

  1. nwrittenの型をint64へ変更:

    • export func Copyn(src Read, dst Write, n int64) (written int64, err *os.Error)
    • これにより、コピー可能なデータサイズの上限が大幅に引き上げられ、大規模なファイルやストリームのコピーに対応できるようになりました。
  2. バッファリングとコピーロジック:

    • 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): 実際に書き込まれたバイト数nwwrittenに加算します。ここでもint64へのキャストが行われます。
    • エラーハンドリングの強化:
      • if ew != nil: 書き込みエラーが発生した場合、そのエラーを返し、ループを終了します。
      • if nr != nw: 読み込んだバイト数と書き込んだバイト数が一致しない場合、os.EIO(I/Oエラー)を返してループを終了します。これは、部分的な書き込みが発生した場合のデータ破損を防ぐための重要なチェックです。
      • if er != nil: 読み込みエラーが発生した場合、そのエラーを返し、ループを終了します。
      • if nr == 0: src.Readが0バイトを返した場合、これはEOFに達したことを意味します。この場合、ErrEOFをエラーとして返し、ループを終了します。これにより、指定されたバイト数に満たないうちにソースが終了したことを明示的に通知します。

Copy関数の追加詳細

  1. シグネチャ: export func Copy(src Read, dst Write) (written int64, err *os.Error)

    • Copynと同様に、コピーされたバイト数をint64で返します。
  2. 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の適応性を示す重要なステップです。

関連リンク

参考にした情報源リンク