io.Readerを使った読み込み
ファイルの読み込みやTCPコネクションのメッセージ読み込みに、io.Reader
インターフェースを実装したstructのRead
メソッドを使う
以下その例
package main import "fmt" import "os" func main() { file, _ := os.Open("sample.txt") bufferSize := 4 buf := make([]byte, bufferSize) n, e := file.Read(buf) fmt.Println("LENGTH READ:", n, "ERROR:", e) fmt.Println("---RESULT---\n", string(buf)) }
問題
上記の例では、読み込んだトークンを流し込むバッファー([]byte
型)を自分で定義して、ReaderのReadに渡している。しかしTCPコネクションのメッセージなど、どのような大きさになる分からない場合に、いたずらにバッファーの大きさ(上記におけるbufferSize)を大きく取るわけにもいかない。もちろん、小さく取りすぎると正しく十分取得できない。
上記の実行例
% go run main.go LENGTH READ: 4 ERROR: <nil> ---RESULT--- abcd
4文字で切れる
bufio.Scannerを使った読み込み
そこでGo言語では、bufio.Scanner
を提供している。このScannerはざっくり言うと「読み込もうとしたioのトークンが途切れるまで読み込む」という機能を持っている。
例
package main import "os" import "fmt" import "bufio" func main() { file, _ := os.Open("sample.txt") scnr := bufio.NewScanner(file) scnr.Scan() fmt.Println(scnr.Text()) }
結果
% go run main2.go abcdefghijklmnopgrstuvwxyz
だいぶラク。うまくいっているように見えるが
「トークンが途切れる」とは?
実はsample.txt
の内容は以下の通り
% cat sample.txt abcdefghijklmnopgrstuvwxyz 0123456789
デフォルトではnewlineで区切られるため、二行目が読み込まれていない
ちゃんと説明を見よう(ハイ
Scanner provides a convenient interface for reading data such as a file of newline-delimited lines of text. Successive calls to the Scan method will step through the 'tokens' of a file, skipping the bytes between the tokens. The specification of a token is defined by a split function of type SplitFunc; the default split function breaks the input into lines with line termination stripped. Split functions are defined in this package for scanning a file into lines, bytes, UTF-8-encoded runes, and space-delimited words. The client may instead provide a custom split function.
Scanner
はファイルなど改行区切りのテキストを読み込むために手軽なインターフェースを提供します。連続的にScan
メソッドを呼ぶと、現在まで読み込んだトークンを破棄し、次のトークンまで読み込みが実行されます。「トークン」の単位はSplitFunc
によって定義されます。デフォルトではこの関数は「行の終端」を検知してトークンを区切ります。Split
関数はこのパッケージ内ではファイルを行・byte列・UTF8文字列・スペース区切り文字列を扱うために定義されています。これらの代わりに、パッケージの使用者はこのルールを独自に実装したSplitFunc
を渡すことができます。
Scanning stops unrecoverably at EOF, the first I/O error, or a token too large to fit in the buffer. When a scan stops, the reader may have advanced arbitrarily far past the last token. Programs that need more control over error handling or large tokens, or must run sequential scans on a reader, should use bufio.Reader instead.
Scanの実行はファイルの終端、I/Oエラー、過大なトークンによって回復せずに終了します。Scanの実行が終了すると、与えられたReaderは不定のトークン位置まで移動します。したがって、繊細なエラーハンドリングや、厳格に連続的なScanが必要なアプリケーションは、bufio.Scanner
ではなくbufio.Reader
を使用すべきです。
とのこと
ハイ。とりあえず今回得たいもの得るだけだったら適当に
package main import "os" import "fmt" import "bufio" func main() { var pool string file, _ := os.Open("sample.txt") scnr := bufio.NewScanner(file) scnr.Scan() pool += scnr.Text() scnr.Scan() pool += "\n" + scnr.Text() fmt.Println(pool) }
雑感
- Go言語の標準パッケージの中身を読むべきだと思っていたが、
- まずはどういうパッケージがあるか俯瞰する必要があるっぽい
- アホみたいに
bufio.Reader
だけで頑張って実装してたパッケージがforkされてScanner
に置き換えられて使われてて、はずかしい思いをした
DRY