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

[インデックス 17214] ファイルの概要

このコミットは、Go言語の標準ライブラリ archive/zip パッケージに File.DataOffset メソッドを追加するものです。このメソッドは、ZIPアーカイブ内のファイルの圧縮されたデータが開始するオフセット(位置)を返します。

コミット

commit a2599cf50e8f45a19c91c2180468ba0c9d96af0e
Author: Brad Fitzpatrick <bradfitz@golang.org>
Date:   Tue Aug 13 16:29:51 2013 -0700

    archive/zip: add File.DataOffset
    
    Accessor to find where the bytes of a file start.
    
    R=golang-dev, rsc, dsymonds, adg
    CC=golang-dev
    https://golang.org/cl/12784045

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/a2599cf50e8f45a19c91c2180468ba0c9d96af0e

元コミット内容

archive/zip: add File.DataOffset Accessor to find where the bytes of a file start.

変更の背景

Go言語の archive/zip パッケージは、ZIPアーカイブの読み書きを扱うための標準ライブラリです。通常、ZIPファイル内のコンテンツを読み取る際には、File.Open() メソッドを使用します。このメソッドは、透過的にデータを解凍し、チェックサムを検証するため、ほとんどのユースケースで十分です。

しかし、特定の高度なシナリオでは、ファイルの実際のデータがZIPアーカイブ内でどこから始まるかという「オフセット」情報が必要になる場合があります。例えば、ZIPファイルの一部だけを読み込みたい場合、またはカスタムのストリーミング処理を行いたい場合などです。File.DataOffset の追加は、このような低レベルなアクセスを可能にし、より柔軟なZIPファイル操作をサポートすることを目的としています。コミットメッセージにある「Accessor to find where the bytes of a file start.」という記述が、この目的を明確に示しています。

前提知識の解説

ZIPファイルフォーマット

ZIPファイルは、複数のファイルやディレクトリを単一のアーカイブにまとめるための一般的なファイルフォーマットです。その構造は、主に以下の要素で構成されます。

  1. ローカルファイルヘッダ (Local File Header): 各ファイルのエントリの先頭に位置し、ファイル名、圧縮方法、圧縮・非圧縮サイズ、CRC-32チェックサムなどの情報を含みます。
  2. ファイルデータ (File Data): ローカルファイルヘッダの直後に続く、実際のファイルコンテンツ(圧縮されている場合もある)です。
  3. データ記述子 (Data Descriptor): オプションで、ファイルデータの後ろに続くことがあります。これは、ストリーミング圧縮などで事前にサイズやCRC-32が不明な場合に使用されます。
  4. セントラルディレクトリファイルヘッダ (Central Directory File Header): ZIPアーカイブの末尾近くに位置し、アーカイブ内の各ファイルのメタデータ(ファイル名、圧縮方法、圧縮・非圧縮サイズ、CRC-32、ローカルファイルヘッダへのオフセットなど)をまとめて格納します。
  5. セントラルディレクトリエンドレコード (End of Central Directory Record): ZIPアーカイブの終端に位置し、セントラルディレクトリの開始位置やサイズ、アーカイブ内のエントリ数などの情報を含みます。

File.DataOffset が返すのは、上記の「ファイルデータ (File Data)」がZIPアーカイブの先頭から何バイト目に位置するかという情報です。

Go言語の archive/zip パッケージ

Go言語の archive/zip パッケージは、これらのZIPファイルフォーマットの構造を抽象化し、Goプログラムから簡単にZIPファイルを扱えるようにする機能を提供します。

  • zip.Reader: ZIPファイルを読み込むための構造体。
  • zip.File: ZIPアーカイブ内の個々のファイルエントリを表す構造体。この構造体には、ファイル名、サイズ、圧縮方法などのメタデータが含まれます。
  • File.Open(): zip.File のメソッドで、ファイルの内容を読み取るための io.ReadCloser を返します。このメソッドは、必要に応じてデータを自動的に解凍します。

技術的詳細

このコミットで追加された File.DataOffset() メソッドは、zip.File 構造体に新しい機能を追加します。

// DataOffset returns the offset of the file's possibly-compressed
// data, relative to the beginning of the zip file.
//
// Most callers should instead use Open, which transparently
// decompresses data and verifies checksums.
func (f *File) DataOffset() (offset int64, err error) {
	bodyOffset, err := f.findBodyOffset()
	if err != nil {
		return
	}
	return f.headerOffset + bodyOffset, nil
}

このメソッドの内部では、以下の計算が行われます。

  1. f.findBodyOffset() の呼び出し: この内部メソッドは、おそらくローカルファイルヘッダのサイズと、それに続くオプションのデータ記述子のサイズを計算し、ファイルデータがローカルファイルヘッダからどれだけオフセットしているか(bodyOffset)を決定します。
  2. f.headerOffset + bodyOffset: f.headerOffset は、この zip.File エントリのローカルファイルヘッダがZIPアーカイブの先頭から何バイト目に位置するかを示す値です。これに bodyOffset を加算することで、最終的にファイルデータがZIPアーカイブの先頭から何バイト目に位置するかという絶対オフセットが計算されます。

この DataOffset は、圧縮されたデータ(または非圧縮データ)の開始位置を直接指します。これにより、開発者は io.ReaderAt などのインターフェースを使用して、ZIPファイル内の特定のデータブロックに直接アクセスできるようになります。これは、例えば、非常に大きなZIPファイルから特定のデータの一部だけを効率的に読み込みたい場合や、カスタムのデータ処理パイプラインを構築する際に有用です。

コミットメッセージにもあるように、ほとんどの一般的なユースケースでは File.Open() を使用することが推奨されます。これは Open() が解凍とチェックサム検証を自動的に行うため、より安全で使いやすいからです。DataOffset() は、より低レベルな制御が必要な場合にのみ使用されるべきです。

コアとなるコードの変更箇所

変更は src/pkg/archive/zip/reader.go ファイルに対して行われました。

--- a/src/pkg/archive/zip/reader.go
+++ b/src/pkg/archive/zip/reader.go
@@ -114,6 +114,19 @@ func (rc *ReadCloser) Close() error {
 	return rc.f.Close()
 }
 
+// DataOffset returns the offset of the file's possibly-compressed
+// data, relative to the beginning of the zip file.
+//
+// Most callers should instead use Open, which transparently
+// decompresses data and verifies checksums.
+func (f *File) DataOffset() (offset int64, err error) {
+	bodyOffset, err := f.findBodyOffset()
+	if err != nil {
+		return
+	}
+	return f.headerOffset + bodyOffset, nil
+}
+
 // Open returns a ReadCloser that provides access to the File's contents.
 // Multiple files may be read concurrently.
 func (f *File) Open() (rc io.ReadCloser, err error) {

コアとなるコードの解説

追加された DataOffset() メソッドは、File 型のレシーバを持つ関数です。

  • func (f *File) DataOffset() (offset int64, err error):

    • f *File: File 構造体へのポインタをレシーバとして受け取ります。これにより、File インスタンスの内部状態(headerOffset など)にアクセスできます。
    • offset int64: ZIPアーカイブの先頭からのデータオフセットを int64 型で返します。
    • err error: 処理中にエラーが発生した場合にエラー情報を返します。
  • bodyOffset, err := f.findBodyOffset():

    • この行は、File 構造体の内部メソッドである findBodyOffset() を呼び出しています。このメソッドは、ZIPファイルフォーマットの仕様に基づいて、ローカルファイルヘッダの終わりから実際のファイルデータが始まるまでのオフセットを計算します。これには、可変長のファイル名やエクストラフィールドの長さ、そしてオプションのデータ記述子の存在が考慮されます。
  • if err != nil { return }:

    • findBodyOffset() の呼び出しでエラーが発生した場合、そのエラーを呼び出し元にそのまま返します。
  • return f.headerOffset + bodyOffset, nil:

    • f.headerOffset: これは File 構造体のフィールドで、このファイルのローカルファイルヘッダがZIPアーカイブの先頭から何バイト目に位置するかを示します。
    • bodyOffset: findBodyOffset() によって計算された、ローカルファイルヘッダの開始位置からファイルデータが始まるまでのオフセットです。
    • これら二つのオフセットを合計することで、ZIPアーカイブの先頭からファイルデータが始まるまでの絶対オフセットが算出されます。エラーがなければ nil を返します。

このメソッドは、ZIPファイルフォーマットの低レベルな詳細を抽象化しつつ、必要に応じてファイルデータへの直接アクセスを可能にする、非常に有用なアクセサを提供します。

関連リンク

参考にした情報源リンク