Goでバイナリを固定長で読んでいくときのメモ

バイナリの先頭nバイトを読みたいときというのがあると思う。先頭4バイトを読むとデータのサイズが入ってて、その後nバイトがデータとか、そういうやつ。固定長で読む良い方法があんまりなくて(あったらおしえてくれ)、自分でsliceを所望のサイズでmakeして、そこにio.Readする。ちょっとはまったのでメモ。

 

  file, _ := os.Open("test.bin")

  // 例1
  buf := make([]byte, 256)
  read_size, _ := file.Read(buf)
  fmt.Println(read_size, "bytes read.", string(buf))

  // 例2
  buf2 := make([]byte, 256)
  read_size2, _ := io.ReadFull(file, buf2)
  fmt.Println(read_size2, "bytes read.", string(buf2))

 

何も考えずにやると上の例1みたいになると思うが、これだとバッファが埋まるまで読んでくれないことがあってはまる(read_sizeが256にならない場合がある)。

そもそも、io.Readは1回呼ばれたときの読み出しサイズが保証されていないので、io.Readで読み出されるサイズはなんかよくわからないけど読み出し元の種類とか状況によるっぽい。数バイトくらいなら問題ないことが多いが、サイズが大きくなってくると1回のio.Readでバッファを埋められないことがでてくる。なので、io.ReadFullを使う(例2)。これならバッファが埋まるまでio.Readを繰り返し呼んでくれる。テキストファイルの読み込みとかだとio.Readを直接使うことはほとんどないが、バイナリを扱うときもできるだけ使わないのがよさそう。

ちなみに[]byteをstringにcastできるらしい。ASCII専用だろうが。

 

あと、2/4/8バイトを読んで数値として解釈するのはもっと簡単で、encoding/binaryをimportして、

  var num uint16
  binary.Read(file, binary.LittleEndian, &num)

とすればnumの型の分(この場合は2バイト)だけ読み出してreaderを進めてくれる。楽。

追記(2016/04/16):

encoding/binary/binary.goを読むと、io.ReadFullを使っていることがわかる。なるほど。

あと、binary.Readは各整数型以外にもそれぞれのスライスに対応しているらしい。

  num := make([]uint16, 3)
  binary.Read(file, binary.LittleEndian, &num)
  fmt.Println(num)
  // => [14648 24880 25442]

2 thoughts on “Goでバイナリを固定長で読んでいくときのメモ

  • 2016/04/16 at 01:37
    Permalink

    []byteをstringにキャストするのは別に文字コード関係なくて、文字コードをケアして文字数とかを処理するにはruneに変換するか、unicode/utf8パッケージ使うか等の必要がありますね。[]runeの変換は、utf8だったら何も気にせず処理できるけど、runeはただのunicodeのcode pointなので複数のcode pointで一文字を表すようなものであれば文字数がずれたりします

    https://golang.org/ref/spec#Conversions_to_and_from_a_string_type

  • 2016/04/26 at 23:58
    Permalink

    なるほど。べつに何であろうが[]byteからstringに変換した後どっかに吐き出したらその先でそれなりに表示されるが、文字レベルで処理をするなら適切に扱う(utf8の場合はruneとかを使う)必要があるってことですね。
    ありがとうございます、勉強になります。

Comments are closed.