[インデックス 15336] ファイルの概要
このコミットは、Go言語の標準ライブラリである mime
パッケージ内の src/pkg/mime/type_unix.go
ファイルに対する変更です。このファイルは、Unix系システムにおいてMIMEタイプ(Multipurpose Internet Mail Extensions)の定義ファイルを読み込み、ファイル拡張子とMIMEタイプとのマッピングを初期化する役割を担っています。具体的には、/etc/mime.types
のようなシステム上のMIMEタイプ定義ファイルを解析し、Goプログラム内で利用できるようにします。
コミット
commit 35367cc64129f1dfcf764ed6ba3461db9bb54343
Author: Rob Pike <r@golang.org>
Date: Wed Feb 20 14:34:03 2013 -0800
mime: use Scanner to read mime files during init
Also close the file when we're done.
R=bradfitz
CC=golang-dev
https://golang.org/cl/7363045
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/35367cc64129f1dfcf764ed6ba3461db9bb54343
元コミット内容
mime: use Scanner to read mime files during init
Also close the file when we're done.
R=bradfitz
CC=golang-dev
https://golang.org/cl/7363045
変更の背景
このコミットの主な目的は、MIMEタイプ定義ファイルを読み込む際の堅牢性とリソース管理を改善することです。以前の実装では bufio.NewReader
と reader.ReadString('\n')
を使用していましたが、これにはいくつかの課題がありました。
- ファイルクローズの確実性:
ReadString
を使用したループ内でエラーが発生した場合、f.Close()
がif err != nil
ブロック内に記述されていたため、エラー発生時にのみファイルが閉じられ、正常終了時には閉じられない可能性がありました。これはリソースリークにつながる可能性があります。 - 効率と簡潔性:
bufio.Reader.ReadString
は、行ごとに新しい文字列を割り当てるため、効率が悪い場合があります。また、行の読み込みとエラーハンドリングのロジックがやや冗長でした。 - Goのイディオム:
bufio.Scanner
は、Goにおいて行指向の入力を処理するためのよりイディオム的で効率的な方法として設計されています。この変更は、Goの標準ライブラリが推奨されるパターンに従うことを目的としています。
これらの課題を解決するため、bufio.Scanner
を導入し、defer
ステートメントを使用してファイルのクローズを確実にし、よりクリーンで効率的なコードを実現しています。
前提知識の解説
MIMEタイプ (Multipurpose Internet Mail Extensions)
MIMEタイプは、インターネット上でやり取りされる様々なデータ形式を識別するための標準的な方法です。例えば、text/plain
はプレーンテキスト、image/jpeg
はJPEG画像を表します。ウェブサーバーは、クライアントにファイルを送信する際に適切なMIMEタイプを付与することで、ブラウザがそのファイルを正しく解釈・表示できるようにします。Goの mime
パッケージは、ファイル拡張子からMIMEタイプを推測したり、その逆を行ったりする機能を提供します。
bufio
パッケージ
bufio
パッケージは、バッファリングされたI/O操作を提供し、I/Oの効率を向上させます。主な型として Reader
と Scanner
があります。
-
bufio.Reader
:io.Reader
をラップし、バッファリング機能を追加します。ReadString(delim byte)
メソッドは、指定されたデリミタ(区切り文字)が見つかるまでデータを読み込み、そのデリミタを含む文字列を返します。エラーが発生した場合でも、それまでに読み込んだデータを返します。- 柔軟性が高いですが、行ごとに新しい文字列を割り当てるため、大量の行を処理する場合にはオーバーヘッドが生じる可能性があります。
-
bufio.Scanner
:io.Reader
からトークン(行、単語など)を読み取るための高レベルなインターフェースを提供します。Scan()
メソッドは、次のトークンが利用可能であればtrue
を返し、それ以上トークンがないかエラーが発生した場合はfalse
を返します。Text()
メソッドは、現在のトークンを文字列として返します。Bytes()
メソッドは、現在のトークンをバイトスライスとして返します。これは内部バッファへの参照を返すため、余分なメモリ割り当てを避けることができ、効率的です。- デフォルトでは、行末の改行文字はトークンに含まれません。
bufio.Scanner
は、特にファイルを行ごとに読み込むような一般的なタスクにおいて、bufio.Reader
よりも簡潔で効率的なコードを書くことができます。
defer
ステートメント
Go言語の defer
ステートメントは、関数がリターンする直前に実行される関数呼び出しをスケジュールします。これは、リソースのクリーンアップ(ファイルのクローズ、ロックの解放など)を確実に行うために非常に便利です。defer
された関数は、関数の実行が正常に完了した場合でも、パニックが発生した場合でも、必ず実行されます。
技術的詳細
このコミットの技術的な核心は、ファイル読み込みのメカニズムを bufio.Reader
から bufio.Scanner
へと変更した点にあります。
-
bufio.Reader
からbufio.Scanner
への移行:- 変更前:
reader := bufio.NewReader(f)
でbufio.Reader
を作成し、reader.ReadString('\n')
で行を読み込んでいました。この方法は、行ごとに新しい文字列が生成されるため、メモリ割り当てのオーバーヘッドがありました。また、ループ内でエラーハンドリングとファイルクローズを明示的に行う必要がありました。 - 変更後:
scanner := bufio.NewScanner(f)
でbufio.Scanner
を作成し、for scanner.Scan()
ループで行を読み込みます。scanner.Scan()
は次の行が読み込めた場合にtrue
を返し、読み込めない場合(EOFまたはエラー)にfalse
を返します。読み込んだ行の内容はscanner.Text()
で取得できます。この方法は、内部バッファを効率的に再利用し、メモリ割り当てを最小限に抑えることができます。
- 変更前:
-
defer f.Close()
の導入:- 変更前は、
f.Close()
がif err != nil
ブロック内にあり、エラーが発生した場合にのみファイルが閉じられる可能性がありました。 - 変更後は、
defer f.Close()
がloadMimeFile
関数の冒頭に追加されました。これにより、loadMimeFile
関数がどのような経路で終了しても(正常終了、エラー、パニック)、ファイルf
は確実に閉じられるようになります。これは、リソースリークを防ぐためのGoのベストプラクティスです。
- 変更前は、
-
scanner.Err()
によるエラーハンドリング:bufio.Scanner
を使用する場合、ループ終了後にscanner.Err()
を呼び出すことで、スキャン中に発生したエラーを確認できます。scanner.Scan()
がfalse
を返した場合、それがEOFによるものか、それともI/Oエラーによるものかを区別するためにscanner.Err()
をチェックすることが重要です。このコミットでは、エラーが発生した場合にpanic(err)
を呼び出すように変更されています。これは、MIMEファイルの読み込みエラーがプログラムの初期化段階で発生した場合、続行できない致命的な問題と見なされるためと考えられます。
これらの変更により、コードはより簡潔になり、リソース管理が改善され、Goのイディオムに沿った堅牢なファイル読み込み処理が実現されています。
コアとなるコードの変更箇所
--- a/src/pkg/mime/type_unix.go
+++ b/src/pkg/mime/type_unix.go
@@ -23,15 +23,11 @@ func loadMimeFile(filename string) {
if err != nil {
return
}
+ defer f.Close()
- reader := bufio.NewReader(f)
- for {
- line, err := reader.ReadString('\n')
- if err != nil {
- f.Close()
- return
- }
- fields := strings.Fields(line)
+ scanner := bufio.NewScanner(f)
+ for scanner.Scan() {
+ fields := strings.Fields(scanner.Text())
if len(fields) <= 1 || fields[0][0] == '#' {
continue
}
@@ -43,6 +39,9 @@ func loadMimeFile(filename string) {
setExtensionType("."+ext, mimeType)
}
}
+ if err := scanner.Err(); err != nil {
+ panic(err)
+ }
}
func initMime() {
コアとなるコードの解説
loadMimeFile
関数は、指定されたMIMEタイプ定義ファイルを読み込み、その内容を解析してMIMEタイプマッピングを設定します。
-
defer f.Close()
の追加:defer f.Close()
ファイル
f
が正常に開かれた後、defer f.Close()
が追加されました。これにより、loadMimeFile
関数が終了する際に、ファイルディスクリプタが確実に閉じられることが保証されます。以前は、エラーハンドリングのブロック内でしかf.Close()
が呼び出されていなかったため、正常終了時にファイルが閉じられない可能性がありました。 -
bufio.Reader
からbufio.Scanner
への置き換え:- reader := bufio.NewReader(f) - for { - line, err := reader.ReadString('\n') - if err != nil { - f.Close() - return - } - fields := strings.Fields(line) + scanner := bufio.NewScanner(f) + for scanner.Scan() { + fields := strings.Fields(scanner.Text())
- 元のコードでは
bufio.NewReader(f)
でリーダーを作成し、無限ループfor {}
の中でreader.ReadString('\n')
を使って1行ずつ読み込んでいました。エラーが発生した場合はループを抜けていました。 - 新しいコードでは
bufio.NewScanner(f)
でスキャナーを作成し、for scanner.Scan()
ループを使用しています。scanner.Scan()
は次の行が正常に読み込まれた場合にtrue
を返し、それ以上読み込むものがないかエラーが発生した場合はfalse
を返してループを終了します。 - 読み込んだ行の内容は
scanner.Text()
で取得され、strings.Fields()
でスペース区切りで分割されます。この変更により、行の読み込みとループの制御がより簡潔かつ効率的になりました。
- 元のコードでは
-
scanner.Err()
によるエラーチェックの追加:+ if err := scanner.Err(); err != nil { + panic(err) + }
for scanner.Scan()
ループが終了した後、scanner.Err()
を呼び出して、スキャン中にエラーが発生したかどうかを確認します。もしエラーがあれば、panic(err)
を呼び出してプログラムを異常終了させます。これは、MIMEファイルの読み込みエラーが回復不能な問題であると判断されたためです。
これらの変更により、MIMEファイルの読み込み処理は、Goのイディオムに沿った、より堅牢でリソース効率の良い実装になりました。
関連リンク
- https://github.com/golang/go/commit/35367cc64129f1dfcf764ed6ba3461db9bb54343
- https://golang.org/cl/7363045 (Gerrit Code Review)
参考にした情報源リンク
- go.dev: bufio.Scanner vs bufio.Reader.ReadString
- labex.io: bufio.Scanner
- medium.com: bufio.Scanner vs bufio.Reader
- dev.to: bufio.Scanner for large files
- stackoverflow.com: bufio.Scanner vs bufio.Reader.ReadString memory
- chriswilcox.dev: bufio.Scanner vs bufio.Reader
- stackoverflow.com: Go defer statement