Goで 1行 1行読み取って処理をしたいときは, bufio.Scanner
を使うのが推奨されています. しかし bufio.Scanner
にはひとつ欠点があり, 長すぎる行が読めないという問題があります. 最長で MaxScanTokenSize(64 * 1024)バイトしか読めず, これ以上長い行を読ませようとするとエラーが返ります. この値は constで定義されているため変更できません. また NewScanner的な関数の引数にサイズを与えて独自のバッファサイズを設定するということもできません. そのため長い行を読む場合は今のところ bufio.Scanner
を使うことはできません.
追記
開発版には bufio.Scanner.Buffer()
メソッドが追加され, ユーザ定義のバッファが Scannerに設定できるようです. これを使えば上限がわかる場合は Scannerだけでも対応できます(任意の長さとなると後述のような方法しかないと思います. Scannerで errorが返ったときさらに大きなバッファを割り当てるという方法もありますが, 同程度に面倒になりそうです).
コメントで @heliac2000 さんに教えていだきました. ありがとうございます.
多くの場合問題にならないと思いますが, すごく長い行を含むファイルが入力として与えられる可能性がある場合は意識しておく必要があるでしょう.
失敗例
標準入力から読み取り, 各行に何バイト含まれているかを出力するプログラムを考えます. 初めに長い行を含むファイルを以下のコマンドで生成しておきます.
% perl -E 'say "x" x 10_000_000' > test.txt
次に bufio.Scanner
を使ったコード例を示します.
package main
import (
"bufio"
"fmt"
"log"
"os"
)
func main() {
s := bufio.NewScanner(os.Stdin)
i := 1
for s.Scan() {
fmt.Printf("%d line length is %d\n", len(s.Text()))
i++
}
if err := s.Err(); err != nil {
log.Fatal(err)
}
}
このプログラムに先ほど生成したファイルを与えてみましょう.
% go run scanner.go < test.txt
2015/12/02 22:50:12 bufio.Scanner: token too long
exit status 1
MaxScanTokenSize
を超えてしまう行を読み込んでしまったため, token too long
というエラーを受け取ってしまいました.
bufio.Scannerを使わない方法
bufio.Reader
の ReadLineメソッドを使います. ドキュメントには通常であれば代わりに bufio.Scanner
を使えとありますが, 使えないのでこちらを使います.
package main
import (
"bufio"
"bytes"
"fmt"
"io"
"log"
"os"
)
func main() {
r := bufio.NewReader(os.Stdin)
for i := 1; ; i++ {
buf, isPrefix, err := r.ReadLine()
if err != nil {
if err != io.EOF {
log.Fatal(err)
}
break
}
bb := bytes.NewBuffer(buf)
if isPrefix {
for {
b, cont, err := r.ReadLine()
if err != nil {
if err != io.EOF {
log.Fatal(err)
}
break
}
if _, err := bb.Write(b); err != nil {
log.Fatal(err)
}
if !cont {
break
}
}
}
fmt.Printf("Length of %d line is %d\n", i, bb.Len())
}
}
bufio.Reader.ReadLine
メソッドは 3つの戻り値があり, それぞれ読み取ったバッファ, 全部読み取れたかどうか, エラーになります. 注目するのは 2つめの戻り値でこれが true
であれば, 行はさらに続くのでさらに読み取りを行い, 初めに得たバッファに追記していきます. これを 2つめの戻り値が falseになるまで続けます. これでどれだけ長い行でも読めるようになります. 実行結果は以下のようになり. 10,000,000文字分読めていることが確認できます.
% go run test.go < test.txt
Length of 1 line is 10000000
おわりに
Goで長い行を読む場合の注意点について示しました.
明日は @Maki-Daisuke さんです.