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

[インデックス 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言語のrunestring

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.BufReadReadRuneメソッドを追加したことです。このメソッドは、以下のロジックで動作します。

  1. バッファの確認と補充: ReadRuneは、まず内部バッファ(b.buf)に十分なデータがあるかを確認します。UTF-8文字は最大でutf8.UTFMaxバイト(このコミットで4バイトと定義)を占める可能性があるため、現在の読み取り位置b.rからutf8.UTFMaxバイト分のデータがバッファの書き込み位置b.wまでに存在するか、または少なくとも完全なruneをデコードできるだけのデータが存在するかをutf8.FullRuneを使って確認します。 もしバッファに十分なデータがない場合、または部分的なUTF-8シーケンスしか含まれていない場合は、b.Fill()を呼び出して基となるリーダーからさらにデータを読み込み、バッファを補充しようとします。

  2. エラーハンドリング: b.Fill()がエラーを返した場合、そのエラーをReadRuneも返します。 バッファ補充後もデータが読み込めず、かつバッファが空の場合(b.r == b.w)、これはファイルの終端(EOF)に達したことを意味するため、EndOfFileエラーを返します。

  3. runeのデコード: バッファに十分なデータが揃ったと判断された後、utf8.DecodeRune関数を使用して、現在の読み取り位置b.rから始まるバイトスライス(b.buf[b.r:b.w])からruneとそれに続くバイト数(size)をデコードします。 ASCII文字(0x80未満のバイト)の場合は、最初の1バイトがそのままruneとなり、サイズは1となります。これは最適化のためです。それ以外の文字はutf8.DecodeRuneに委ねられます。

  4. 読み取り位置の更新: デコードが完了したら、読み取ったバイト数sizeだけb.r(読み取り位置)を進めます。

  5. 結果の返却: デコードされた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の変更点

  1. utf8パッケージのインポート: bufioパッケージがutf8パッケージの機能を利用するために、インポートリストに"utf8"が追加されました。これは、ReadRuneメソッド内でutf8.FullRuneutf8.DecodeRuneを使用するために必要です。

  2. 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の変更点

  1. UTFMax定数の追加: export const ( ... )ブロック内にUTFMax = 4;が追加されました。これは、UTF-8エンコーディングにおいて1つのUnicodeコードポイントが占める最大バイト数(4バイト)を定義するものです。この定数は、bufio.ReadRuneのようなUTF-8処理を行う関数で、バッファのサイズ計算や部分的なシーケンスの検出に利用されます。

これらの変更により、Go言語のI/OシステムはUnicode文字をより効率的かつ正確に処理できるようになり、国際化対応のアプリケーション開発の基盤が強化されました。

関連リンク

参考にした情報源リンク

  • Go言語の公式ドキュメント
  • UTF-8エンコーディングの仕様
  • Go言語の初期のコミット履歴(GitHub)
  • Go言語のbufioおよびutf8パッケージのソースコード