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

[インデックス 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.NewReaderreader.ReadString('\n') を使用していましたが、これにはいくつかの課題がありました。

  1. ファイルクローズの確実性: ReadString を使用したループ内でエラーが発生した場合、f.Close()if err != nil ブロック内に記述されていたため、エラー発生時にのみファイルが閉じられ、正常終了時には閉じられない可能性がありました。これはリソースリークにつながる可能性があります。
  2. 効率と簡潔性: bufio.Reader.ReadString は、行ごとに新しい文字列を割り当てるため、効率が悪い場合があります。また、行の読み込みとエラーハンドリングのロジックがやや冗長でした。
  3. 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の効率を向上させます。主な型として ReaderScanner があります。

  • 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 へと変更した点にあります。

  1. 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() で取得できます。この方法は、内部バッファを効率的に再利用し、メモリ割り当てを最小限に抑えることができます。
  2. defer f.Close() の導入:

    • 変更前は、f.Close()if err != nil ブロック内にあり、エラーが発生した場合にのみファイルが閉じられる可能性がありました。
    • 変更後は、defer f.Close()loadMimeFile 関数の冒頭に追加されました。これにより、loadMimeFile 関数がどのような経路で終了しても(正常終了、エラー、パニック)、ファイル f は確実に閉じられるようになります。これは、リソースリークを防ぐためのGoのベストプラクティスです。
  3. 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タイプマッピングを設定します。

  1. defer f.Close() の追加:

    	defer f.Close()
    

    ファイル f が正常に開かれた後、defer f.Close() が追加されました。これにより、loadMimeFile 関数が終了する際に、ファイルディスクリプタが確実に閉じられることが保証されます。以前は、エラーハンドリングのブロック内でしか f.Close() が呼び出されていなかったため、正常終了時にファイルが閉じられない可能性がありました。

  2. 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() でスペース区切りで分割されます。この変更により、行の読み込みとループの制御がより簡潔かつ効率的になりました。
  3. scanner.Err() によるエラーチェックの追加:

    +	if err := scanner.Err(); err != nil {
    +		panic(err)
    +	}
    

    for scanner.Scan() ループが終了した後、scanner.Err() を呼び出して、スキャン中にエラーが発生したかどうかを確認します。もしエラーがあれば、panic(err) を呼び出してプログラムを異常終了させます。これは、MIMEファイルの読み込みエラーが回復不能な問題であると判断されたためです。

これらの変更により、MIMEファイルの読み込み処理は、Goのイディオムに沿った、より堅牢でリソース効率の良い実装になりました。

関連リンク

参考にした情報源リンク