[インデックス 15367] ファイルの概要
このコミットは、Go言語の標準ライブラリbufio
パッケージにおけるScanner
型の利用促進を目的としています。具体的には、Scanner
の新しい使用例をexample_test.go
ファイルとして追加し、既存のReader
型のReadLine
、ReadBytes
、ReadString
メソッドのドキュメントにScanner
の使用を推奨する記述を追記しています。これにより、開発者がより簡単にScanner
の利便性を理解し、適切な場面で利用できるようになります。
コミット
- コミットハッシュ:
dbd409afb58b8fcd315afedd097a85b65f80387d
- Author: Rob Pike r@golang.org
- Date: Thu Feb 21 15:55:40 2013 -0800
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/dbd409afb58b8fcd315afedd097a85b65f80387d
元コミット内容
bufio: add examples for Scanner
Mention Scanner in docs for ReadLine etc.
R=golang-dev, bradfitz
CC=golang-dev
https://golang.org/cl/7375045
変更の背景
bufio
パッケージのScanner
型は、入力ストリームをトークン(行、単語など)に分割して読み取るための強力で便利なインターフェースを提供します。しかし、既存のReader
型のメソッド(ReadLine
, ReadBytes
, ReadString
)と比較して、その利便性や適切な使用方法が十分に認知されていない可能性がありました。
このコミットの背景には、以下の目的があったと考えられます。
Scanner
の利用促進:ReadLine
などの低レベルなメソッドよりも、多くの一般的なテキスト処理タスクにおいてScanner
がより安全で効率的であることを開発者に示す。- ドキュメントの改善: 既存のドキュメントに
Scanner
への言及を追加することで、開発者が適切なツールを選択する際のガイダンスを強化する。 - 具体的な使用例の提供:
Scanner
の基本的な使い方から、カスタムの分割関数(Split function)を用いた高度な使い方まで、具体的なコード例を示すことで、開発者が自身のプロジェクトにScanner
を導入しやすくする。
特に、ReadLine
はバッファの長さに応じて部分的な行を返す可能性があるなど、扱いが難しい側面があります。Scanner
はこのような複雑さを抽象化し、よりシンプルで堅牢な入力処理を可能にします。このコミットは、その優位性を明確にし、開発者がより良いプラクティスを採用するよう促すものです。
前提知識の解説
このコミットを理解するためには、Go言語のio
パッケージとbufio
パッケージに関する基本的な知識が必要です。
io
パッケージ
Go言語における入出力操作の基本的なインターフェースを提供します。
io.Reader
: データを読み取るためのインターフェース。Read(p []byte) (n int, err error)
メソッドを持ちます。io.Writer
: データを書き込むためのインターフェース。Write(p []byte) (n int, err error)
メソッドを持ちます。
bufio
パッケージ
bufio
パッケージは、io.Reader
やio.Writer
インターフェースをラップし、バッファリングされたI/Oを提供することで、効率的な読み書きを可能にします。これにより、システムコール(ディスクI/Oなど)の回数を減らし、パフォーマンスを向上させます。
bufio.Reader
bufio.Reader
は、バッファリングされた読み取り機能を提供します。主なメソッドには以下のようなものがあります。
ReadLine() (line []byte, isPrefix bool, err error)
: 1行を読み取ります。行がバッファに収まらない場合、isPrefix
がtrue
になり、行が分割されて返されることがあります。これは低レベルなプリミティブであり、通常はより高レベルなメソッドの使用が推奨されます。ReadBytes(delim byte) (line []byte, err error)
: 指定された区切り文字(delim
)が出現するまでデータを読み取り、バイトスライスとして返します。ReadString(delim byte) (line string, err error)
:ReadBytes
と同様ですが、結果を文字列として返します。
これらのメソッドは、区切り文字の処理やエラーハンドリングを呼び出し側で適切に行う必要があります。特にReadLine
のisPrefix
の扱いは複雑になりがちです。
bufio.Scanner
bufio.Scanner
は、io.Reader
からデータを読み取り、定義されたルール(分割関数)に基づいてトークン(行、単語など)に分割するための高レベルなインターフェースです。Scanner
は、ReadLine
などの低レベルなメソッドが持つ複雑さを抽象化し、よりシンプルでイディオマティックな方法で入力処理を行うことを可能にします。
Scanner
の主要なメソッドと概念:
bufio.NewScanner(r io.Reader)
: 新しいScanner
を作成します。scanner.Scan() bool
: 次のトークンを読み取ります。読み取りが成功した場合はtrue
を、入力の終端に達したかエラーが発生した場合はfalse
を返します。通常、for scanner.Scan() { ... }
のループで使用されます。scanner.Text() string
: 現在のトークンを文字列として返します。scanner.Bytes() []byte
: 現在のトークンをバイトスライスとして返します。scanner.Err() error
:Scan()
がfalse
を返した後に、エラーが発生した場合はそのエラーを返します。scanner.Split(splitFunc ScanFunc)
: トークンの分割方法を定義する分割関数を設定します。bufio
パッケージには、いくつかの組み込みの分割関数が用意されています。bufio.ScanLines
: 入力を改行文字で分割し、各行をトークンとします。bufio.ScanWords
: 入力を空白文字で分割し、各単語をトークンとします。bufio.ScanBytes
: 入力を1バイトずつ分割し、各バイトをトークンとします。bufio.ScanRunes
: 入力をUTF-8エンコードされたルーン(Unicodeコードポイント)で分割し、各ルーンをトークンとします。 また、カスタムのScanFunc
を実装することで、独自の分割ロジックを定義することも可能です。
Scanner
は、内部でバッファリングとエラーハンドリングを適切に行うため、開発者は入力処理のロジックに集中できます。
技術的詳細
このコミットの技術的詳細は、主にbufio.go
のドキュメント更新と、example_test.go
でのScanner
の具体的な使用例の追加に集約されます。
bufio.go
の変更
bufio.go
では、Reader
型のReadLine
、ReadBytes
、ReadString
メソッドのコメントが更新されました。これらのメソッドは低レベルな操作を提供しますが、多くの場合、Scanner
がより適切な選択肢であることを示唆する文言が追加されています。
ReadLine
のコメントに「Most callers should use ReadBytes('\\n') or ReadString('\\n') instead or use a Scanner.
」という記述が追加されました。これは、ReadLine
が低レベルであり、より高レベルなReadBytes
やReadString
、あるいはScanner
の使用が推奨されることを明確にしています。ReadBytes
とReadString
のコメントに「For simple uses, a Scanner may be more convenient.
」という記述が追加されました。これにより、単純なユースケースではScanner
がより便利であることを示唆しています。
これらの変更は、開発者がこれらのメソッドのドキュメントを参照した際に、Scanner
というより良い選択肢があることを認識させるためのものです。
example_test.go
の追加
example_test.go
は、bufio.Scanner
の様々な使用方法を示すための新しいテストファイルです。Go言語の_test.go
ファイルにExample
関数を記述することで、go test
コマンド実行時にその例が実行され、出力が検証されます。これは、ライブラリの機能を示すための非常に効果的な方法です。
追加された例は以下の3つです。
-
ExampleScanner_lines()
:- 標準入力(
os.Stdin
)から行を読み取る最も基本的なScanner
の使用例です。 bufio.NewScanner(os.Stdin)
でScanner
を初期化し、for scanner.Scan() { ... }
ループで各行を処理します。scanner.Text()
で現在の行の内容を取得し、fmt.Println
で出力します。- エラーハンドリングとして
scanner.Err()
を使用しています。 - これは、ファイルや標準入力から行単位でデータを処理する際の典型的なパターンを示しています。
- 標準入力(
-
ExampleScanner_words()
:- 入力ストリームを単語に分割し、単語数をカウントする例です。
strings.NewReader(input)
を使用して人工的な入力ソースを作成しています。scanner.Split(bufio.ScanWords)
を呼び出すことで、Scanner
が空白文字で入力を分割するように設定しています。for scanner.Scan() { ... }
ループで各単語を処理し、カウントを増やしています。- この例は、
Scanner
のSplit
メソッドを使って、組み込みの分割関数(ScanWords
)を適用する方法を示しています。
-
ExampleScanner_custom()
:- カスタムの分割関数を使用して、32ビットの10進数入力を検証する高度な例です。
bufio.ScanWords
をラップする形でカスタムのsplit
関数を定義しています。- このカスタム関数は、まず
bufio.ScanWords
で単語を抽出し、その後strconv.ParseInt
を使ってその単語が32ビット整数としてパース可能かどうかを検証しています。パースに失敗した場合は、エラーを返します。 scanner.Split(split)
でこのカスタム関数を設定しています。- この例は、
Scanner
の柔軟性を示しており、開発者が独自の複雑な解析ロジックをSplit
関数として組み込むことができることを実演しています。特に、エラーを返すことで、不正な入力があった場合にScanner
が処理を停止し、エラーを報告できる点が重要です。
これらの例は、Scanner
が提供するシンプルさ、柔軟性、そして堅牢性を具体的に示しており、開発者が自身のニーズに合わせてScanner
を効果的に利用するための強力なリファレンスとなります。
コアとなるコードの変更箇所
このコミットによるコードの変更は、以下の2つのファイルにわたります。
-
src/pkg/bufio/bufio.go
:ReadLine
メソッドのコメントの変更:--- a/src/pkg/bufio/bufio.go +++ b/src/pkg/bufio/bufio.go @@ -278,7 +278,7 @@ func (b *Reader) ReadSlice(delim byte) (line []byte, err error) { } // ReadLine is a low-level line-reading primitive. Most callers should use -// ReadBytes('\n') or ReadString('\n') instead. +// ReadBytes('\n') or ReadString('\n') instead or use a Scanner. // // ReadLine tries to return a single line, not including the end-of-line bytes. // If the line was too long for the buffer then isPrefix is set and the
ReadBytes
メソッドのコメントの変更:--- a/src/pkg/bufio/bufio.go +++ b/src/pkg/bufio/bufio.go @@ -331,6 +331,7 @@ func (b *Reader) ReadLine() (line []byte, isPrefix bool, err error) { // it returns the data read before the error and the error itself (often io.EOF). // ReadBytes returns err != nil if and only if the returned data does not end in // delim. +// For simple uses, a Scanner may be more convenient. func (b *Reader) ReadBytes(delim byte) (line []byte, err error) { // Use ReadSlice to look for array, // accumulating full buffers.
ReadString
メソッドのコメントの変更:--- a/src/pkg/bufio/bufio.go +++ b/src/pkg/bufio/bufio.go @@ -378,6 +379,7 @@ func (b *Reader) ReadBytes(delim byte) (line []byte, err error) { // it returns the data read before the error and the error itself (often io.EOF). // ReadString returns err != nil if and only if the returned data does not end in // delim. +// For simple uses, a Scanner may be more convenient. func (b *Reader) ReadString(delim byte) (line string, err error) { bytes, err := b.ReadBytes(delim) return string(bytes), err
-
src/pkg/bufio/example_test.go
:- このファイルは新規作成され、
bufio.Scanner
の3つの使用例(ExampleScanner_lines
,ExampleScanner_words
,ExampleScanner_custom
)が追加されました。 - ファイル全体が新規追加されたため、差分は全行の追加となります。
- このファイルは新規作成され、
コアとなるコードの解説
bufio.go
のドキュメント変更
これらの変更は、既存のReader
メソッドのドキュメントにScanner
への参照を追加することで、開発者がより適切なツールを選択できるようにするためのものです。
ReadLine
は「低レベルなプリミティブ」であり、ほとんどの呼び出し元はReadBytes('\n')
やReadString('\n')
、あるいはScanner
を使用すべきであると明記されました。これは、ReadLine
がisPrefix
フラグの処理を必要とするなど、複雑な側面を持つためです。ReadBytes
とReadString
については、「単純な使用例では、Scanner
の方がより便利かもしれない」と追記されました。これは、これらのメソッドが特定の区切り文字までの読み取りには適しているものの、行や単語といった論理的なトークン単位での処理にはScanner
がより適していることを示唆しています。
これらの変更は、コードの動作自体には影響を与えませんが、Goのドキュメントがコードの利用方法に関する重要なガイダンスを提供するという哲学に基づいています。
example_test.go
の新規追加
このファイルは、bufio.Scanner
の具体的な使用方法をコード例で示すことで、開発者がScanner
の機能を理解し、自身のアプリケーションに適用するのを助けます。
ExampleScanner_lines()
func ExampleScanner_lines() {
scanner := bufio.NewScanner(os.Stdin)
for scanner.Scan() {
fmt.Println(scanner.Text()) // Println will add back the final '\n'
}
if err := scanner.Err(); err != nil {
fmt.Fprintln(os.Stdout, "reading standard input:", err)
}
}
この例は、Scanner
の最も一般的な使用パターンを示しています。os.Stdin
(標準入力)を行単位で読み取り、各行をfmt.Println
で出力します。scanner.Scan()
が次のトークン(この場合は行)を読み取り、成功すればtrue
を返します。ループ内でscanner.Text()
を呼び出すことで、読み取られた行の内容を文字列として取得できます。ループ終了後、scanner.Err()
をチェックすることで、読み取り中に発生したエラーを検出できます。
ExampleScanner_words()
func ExampleScanner_words() {
// An artificial input source.
const input = "Now is the winter of our discontent,\nMade glorious summer by this sun of York.\n"
scanner := bufio.NewScanner(strings.NewReader(input))
// Set the split function for the scanning operation.
scanner.Split(bufio.ScanWords)
// Count the words.
count := 0
for scanner.Scan() {
count++
}
if err := scanner.Err(); err != nil {
fmt.Fprintln(os.Stdout, "reading input:", err)
}
fmt.Printf("%d\n", count)
// Output: 15
}
この例では、Scanner
のSplit
メソッドの利用方法を示しています。scanner.Split(bufio.ScanWords)
を呼び出すことで、Scanner
は入力を空白文字で区切られた単語として認識するようになります。これにより、単語数を簡単にカウントするような処理が実現できます。strings.NewReader(input)
は、文字列をio.Reader
として扱うための便利な方法です。
ExampleScanner_custom()
func ExampleScanner_custom() {
// An artificial input source.
const input = "1234 5678 1234567901234567890"
scanner := bufio.NewScanner(strings.NewReader(input))
// Create a custom split function by wrapping the existing ScanWords function.
split := func(data []byte, atEOF bool) (advance int, token []byte, err error) {
advance, token, err = bufio.ScanWords(data, atEOF)
if err == nil && token != nil {
_, err = strconv.ParseInt(string(token), 10, 32)
}
return
}
// Set the split function for the scanning operation.
scanner.Split(split)
// Validate the input
for scanner.Scan() {
fmt.Printf("%s\n", scanner.Text())
}
if err := scanner.Err(); err != nil {
fmt.Printf("Invalid input: %s", err)
}
// Output:
// 1234
// 5678
// Invalid input: strconv.ParseInt: parsing "1234567901234567890": value out of range
}
この例は、Scanner
の最も強力な機能の一つであるカスタム分割関数の使用を示しています。split
という匿名関数が定義されており、これはbufio.ScanFunc
インターフェースを満たします。このカスタム関数は、まずbufio.ScanWords
を使って単語を抽出し、その後strconv.ParseInt
でその単語が32ビット整数として有効かどうかを検証します。もしパースに失敗した場合(例:数値が32ビットの範囲を超える場合)、エラーを返します。scanner.Split(split)
でこのカスタム関数を設定することで、Scanner
は単語の抽出と同時にその単語のバリデーションも行うようになります。これにより、不正な入力があった場合にscanner.Err()
を通じてエラーが報告され、処理が適切に停止します。この例は、複雑な入力フォーマットの解析やバリデーションにScanner
をどのように活用できるかを示しています。
関連リンク
- Go CL 7375045: https://golang.org/cl/7375045
参考にした情報源リンク
- Go言語
bufio
パッケージ公式ドキュメント: https://pkg.go.dev/bufio - Go言語
io
パッケージ公式ドキュメント: https://pkg.go.dev/io - Go言語
strings
パッケージ公式ドキュメント: https://pkg.go.dev/strings - Go言語
strconv
パッケージ公式ドキュメント: https://pkg.go.dev/strconv