1. syohex

    Posted

    syohex
Changes in title
+すごく長い行を読む場合の注意点
Changes in tags
Changes in body
Source | HTML | Preview
@@ -0,0 +1,117 @@
+Goで 1行 1行読み取って処理をしたいときは, `bufio.Scanner`を使うのが推奨されています. しかし `bufio.Scanner`にはひとつ欠点があり, 長すぎる行が読めないという問題があります. 最長で MaxScanTokenSize(64 * 1024)バイトしか読めず, これ以上長い行を読ませようとするとエラーが返ります. この値は constで定義されているため変更できません. また NewScanner的な関数の引数にサイズを与えて独自のバッファサイズを設定するということもできません. そのため長い行を読む場合は今のところ `bufio.Scanner`を使うことはできません.
+
+多くの場合問題にならないと思いますが, すごく長い行を含むファイルが入力として与えられる可能性がある場合は意識しておく必要があるでしょう.
+
+
+## 失敗例
+
+標準入力から読み取り, 各行に何バイト含まれているかを出力するプログラムを考えます. 初めに長い行を含むファイルを以下のコマンドで生成しておきます.
+
+
+```
+% perl -E 'say "x" x 10_000_000' > test.txt
+```
+
+次に `bufio.Scanner`を使ったコード例を示します.
+
+```go
+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`を使えとありますが, 使えないのでこちらを使います.
+
+```go
+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 さんです.