[インデックス 1228] ファイルの概要
このコミットは、Go言語の標準ライブラリであるbufio
パッケージにReadRune
メソッドを追加し、バッファリングされた入力ストリームから単一のUnicode文字(rune)を読み取る機能を提供します。また、utf8
パッケージにUTFMax
定数を追加し、UTF-8エンコーディングにおける最大バイト数を示しています。
コミット
commit 508277debe6230ef210cf0165c393e6ff2fd0d4b
Author: Robert Griesemer <gri@golang.org>
Date: Mon Nov 24 12:35:07 2008 -0800
bufio.ReadRune
R=rsc
DELTA=32 (29 added, 0 deleted, 3 changed)
OCL=19809
CL=19913
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/508277debe6230ef210cf0165c393e6ff2fd0d4b
元コミット内容
bufio.ReadRune
R=rsc
DELTA=32 (29 added, 0 deleted, 3 changed)
OCL=19809
CL=19913
変更の背景
Go言語は設計当初からUnicodeをネイティブにサポートしており、文字列はUTF-8でエンコードされたバイト列として扱われます。しかし、I/O操作において、バイトストリームから可変長であるUTF-8エンコードされたUnicode文字(rune)を効率的かつ正確に読み取るためのメカニズムが必要でした。特に、bufio
パッケージのようなバッファリングされたリーダーでは、部分的なUTF-8シーケンスがバッファの境界をまたぐ可能性があるため、これを適切に処理する機能が不可欠です。
このコミットは、bufio.BufRead
(現在のbufio.Reader
に相当)にReadRune
メソッドを追加することで、この課題を解決しようとしています。これにより、開発者はバイト単位ではなく、Unicode文字単位で入力ストリームを処理できるようになり、多言語対応のアプリケーション開発が容易になります。また、utf8
パッケージにUTFMax
定数を導入することで、UTF-8文字が最大で何バイトを占めるかという情報が明確に定義され、ReadRune
の実装やその他のUTF-8関連処理の堅牢性が向上します。
前提知識の解説
Go言語のrune
とstring
Go言語において、string
型はUTF-8でエンコードされたバイトの不変なシーケンスです。string
はバイトの集合であり、文字の集合ではありません。一方、rune
型はGo言語におけるUnicodeコードポイントを表すエイリアス型であり、実体はint32
です。UTF-8は可変長エンコーディングであり、1つのUnicodeコードポイント(rune)は1バイトから4バイトの範囲でエンコードされます。
bufio
パッケージ
bufio
パッケージは、I/O操作を効率化するためのバッファリング機能を提供します。bufio.Reader
(当時のbufio.BufRead
)は、基となるio.Reader
からデータを読み込み、内部バッファに格納します。これにより、少量のデータを頻繁に読み取る際に発生するシステムコールオーバーヘッドを削減し、パフォーマンスを向上させます。
utf8
パッケージ
utf8
パッケージは、UTF-8エンコーディングされたテキストを操作するためのユーティリティ関数を提供します。これには、バイトスライスからruneをデコードするDecodeRune
や、バイトスライスが完全なUTF-8シーケンスを含んでいるかを確認するFullRune
などの関数が含まれます。
UTF-8の可変長エンコーディング
UTF-8は、ASCII文字(U+0000からU+007F)を1バイトで表現し、それ以外のUnicode文字を2バイトから4バイトで表現する可変長エンコーディングです。この可変長性のため、バイトストリームから文字を読み取る際には、その文字が何バイトで構成されているかを判断する必要があります。また、バッファリングされたI/Oでは、文字の途中でバッファが尽きる(部分的なUTF-8シーケンスになる)可能性があるため、次の読み取りで残りのバイトを取得し、完全な文字としてデコードするロジックが必要です。
技術的詳細
このコミットの主要な変更点は、bufio.BufRead
にReadRune
メソッドを追加したことです。このメソッドは、以下のロジックで動作します。
-
バッファの確認と補充:
ReadRune
は、まず内部バッファ(b.buf
)に十分なデータがあるかを確認します。UTF-8文字は最大でutf8.UTFMax
バイト(このコミットで4バイトと定義)を占める可能性があるため、現在の読み取り位置b.r
からutf8.UTFMax
バイト分のデータがバッファの書き込み位置b.w
までに存在するか、または少なくとも完全なruneをデコードできるだけのデータが存在するかをutf8.FullRune
を使って確認します。 もしバッファに十分なデータがない場合、または部分的なUTF-8シーケンスしか含まれていない場合は、b.Fill()
を呼び出して基となるリーダーからさらにデータを読み込み、バッファを補充しようとします。 -
エラーハンドリング:
b.Fill()
がエラーを返した場合、そのエラーをReadRune
も返します。 バッファ補充後もデータが読み込めず、かつバッファが空の場合(b.r == b.w
)、これはファイルの終端(EOF)に達したことを意味するため、EndOfFile
エラーを返します。 -
runeのデコード: バッファに十分なデータが揃ったと判断された後、
utf8.DecodeRune
関数を使用して、現在の読み取り位置b.r
から始まるバイトスライス(b.buf[b.r:b.w]
)からruneとそれに続くバイト数(size
)をデコードします。 ASCII文字(0x80未満のバイト)の場合は、最初の1バイトがそのままruneとなり、サイズは1となります。これは最適化のためです。それ以外の文字はutf8.DecodeRune
に委ねられます。 -
読み取り位置の更新: デコードが完了したら、読み取ったバイト数
size
だけb.r
(読み取り位置)を進めます。 -
結果の返却: デコードされたrune、そのバイト数
size
、およびnil
エラーを返します。
また、src/lib/utf8.go
にはUTFMax = 4
という定数が追加されました。これは、UTF-8エンコーディングにおける1つのUnicodeコードポイントの最大バイト長を明示的に定義するものです。この定数は、bufio.ReadRune
の実装において、バッファから読み込むべき最大バイト数を判断するために利用されます。
コアとなるコードの変更箇所
src/lib/bufio.go
--- a/src/lib/bufio.go
+++ b/src/lib/bufio.go
@@ -3,8 +3,12 @@
// license that can be found in the LICENSE file.
package bufio
-import "os"
-import "io"
+
+import (
+ "os";
+ "io";
+ "utf8";
+)
// TODO:
@@ -65,7 +69,7 @@ func (b *BufRead) Fill() *os.Error {
}
// Slide existing data to beginning.
- if b.w > b.r {
+ if b.w > b.r {
CopySlice(b.buf[0:b.w-b.r], b.buf[b.r:b.w]);
b.w -= b.r;
} else {
@@ -140,6 +144,30 @@ func (b *BufRead) UnreadByte() *os.Error {\n return nil
}\n
+// Read a single Unicode character; returns the rune and its size.\n+func (b *BufRead) ReadRune() (rune int, size int, err *os.Error) {\n+\tfor b.r + utf8.UTFMax > b.w && !utf8.FullRune(b.buf[b.r:b.w]) {\n+\t\tn := b.w - b.r;\n+\t\tb.Fill();\n+\t\tif b.err != nil {\n+\t\t\treturn 0, 0, b.err\n+\t\t}\n+\t\tif b.w - b.r == n {\n+\t\t\t// no bytes read\n+\t\t\tif b.r == b.w {\n+\t\t\t\treturn 0, 0, EndOfFile\n+\t\t\t}\n+\t\t\tbreak;\n+\t\t}\n+\t}\n+\trune, size = int(b.buf[b.r]), 1;\n+\tif rune >= 0x80 {\n+\t\trune, size = utf8.DecodeRune(b.buf[b.r:b.w]);\n+\t}\n+\tb.r += size;\n+\treturn rune, size, nil\n+}\n+\n // Helper function: look for byte c in array p,\n // returning its index or -1.\n func FindByte(p *[]byte, c byte) int {\n```
### `src/lib/utf8.go`
```diff
--- a/src/lib/utf8.go
+++ b/src/lib/utf8.go
@@ -7,6 +7,7 @@
package utf8
export const (\n+\tUTFMax = 4;\n \tRuneError = 0xFFFD;\n \tRuneSelf = 0x80;\n \tRuneMax = 1<<21 - 1;\
コアとなるコードの解説
src/lib/bufio.go
の変更点
-
utf8
パッケージのインポート:bufio
パッケージがutf8
パッケージの機能を利用するために、インポートリストに"utf8"
が追加されました。これは、ReadRune
メソッド内でutf8.FullRune
やutf8.DecodeRune
を使用するために必要です。 -
BufRead.ReadRune()
メソッドの追加: このメソッドは、バッファリングされた入力から単一のUnicode文字(rune)を読み取るためのものです。for
ループは、バッファに完全なruneをデコードするのに十分なバイトがない場合(b.r + utf8.UTFMax > b.w
)や、バッファの現在の内容が完全なUTF-8シーケンスではない場合(!utf8.FullRune(b.buf[b.r:b.w])
)に、b.Fill()
を呼び出してバッファを補充します。b.Fill()
がエラーを返した場合、そのエラーを即座に返します。b.Fill()
を呼び出したにもかかわらず、バッファの読み取り位置と書き込み位置が変わらない(つまり、新しいバイトが読み込まれなかった)場合、かつバッファが空であれば、ファイルの終端(EndOfFile
)として扱います。- ループを抜けた後、最初のバイトがASCII文字(
rune < 0x80
)であれば、runeは最初のバイトの値、サイズは1とします。これは高速パスです。 - そうでなければ、
utf8.DecodeRune(b.buf[b.r:b.w])
を呼び出して、バッファからruneとバイト数をデコードします。 - 最後に、読み取ったバイト数
size
だけb.r
(読み取りポインタ)を進め、デコードされたrune、サイズ、およびnil
エラーを返します。
src/lib/utf8.go
の変更点
UTFMax
定数の追加:export const ( ... )
ブロック内にUTFMax = 4;
が追加されました。これは、UTF-8エンコーディングにおいて1つのUnicodeコードポイントが占める最大バイト数(4バイト)を定義するものです。この定数は、bufio.ReadRune
のようなUTF-8処理を行う関数で、バッファのサイズ計算や部分的なシーケンスの検出に利用されます。
これらの変更により、Go言語のI/OシステムはUnicode文字をより効率的かつ正確に処理できるようになり、国際化対応のアプリケーション開発の基盤が強化されました。
関連リンク
- Go言語の
bufio
パッケージ: https://pkg.go.dev/bufio - Go言語の
utf8
パッケージ: https://pkg.go.dev/unicode/utf8 - UnicodeとUTF-8に関する情報: https://ja.wikipedia.org/wiki/UTF-8
参考にした情報源リンク
- Go言語の公式ドキュメント
- UTF-8エンコーディングの仕様
- Go言語の初期のコミット履歴(GitHub)
- Go言語の
bufio
およびutf8
パッケージのソースコード