golangは低レベルな処理(バイナリの取り扱いとかね)もちゃんと行うことができる.
基本(byte型を使う)
golangは組み込みでbyte型が用意されている.
これはその名の通りバイトを扱うための型で, 配列やスライスにすれば単純なバイトストリームを扱える.
package main import ( "fmt" ) func main() { b := []byte{0xDe, 0xaD, 0xBe, 0xeF} fmt.Printf("%b\n", b) // 2進表示 fmt.Printf("%d\n", b) // 10進表示 fmt.Printf("%x\n", b) // 16進小文字表示 fmt.Printf("%X\n", b) // 16進大文字表示 }
[実行結果]
[11011110 10101101 10111110 11101111] [222 173 190 239] deadbeef DEADBEEF
bytesパッケージ
byteのスライスを扱う場面は多いので, 標準で操作するパッケージが用意されている.
(Package bytes)https://golang.org/pkg/bytes/
以下のように比較も簡単だし, 文字列操作を意識しているのかその手の関数が多い.
(byteのスライスはstringとしてみなせるし)
package main import ( "fmt" "bytes" ) func main() { b := []byte{0xDe, 0xaD, 0xBe, 0xeF} b2 := []byte{0xDe, 0xaD, 0x00, 0x00} b3 := []byte{0xDe, 0xaD, 0xBe, 0xeF} c := bytes.Compare(b, b2) // => 1 fmt.Println(c) c2 := bytes.Compare(b, b3) // => 0 fmt.Println(c2) }
[実行結果]
1 0
ファイルに書いてみる
ファイルはただのバイト列なので, ファイルにも書けますね.
package main import ( "io/ioutil" ) func main() { b := []byte{0xDe, 0xaD, 0xBe, 0xeF} ioutil.WriteFile("test.bin", b, 0644) }
[実行結果]
Endianの都合で逆になっちゃってますが...(Windowsはリトルエンディアン)
$ od -x test.bin 0000000 adde efbe 0000004
バッファを扱う(bytes.bufferを使う)
バイトストリームを操作する時にイチイチ操作関数を使っていると面倒でしょうがないです.
io.Readerやio.Writerの操作メソッドを使いたいですね.
あと, いきなりファイルに書いたりせずにバッファリングしたい場面がほとんどです.
bytesパッケージにはbufferという構造体が用意されています.
標準入出力のテストにも使えます.
以下の様なパッケージがあるとして
// 挨拶するパッケージ package hello import ( "fmt" "io" ) func hello(out io.Writer) { fmt.Fprintf(out, "hello, world") }
上記のテストコードは以下になります.
bufferへ書き出して比較するという感じです.
helloの引数にinterfaceを使ったおかげでいい感じになります.
package hello import ( "bytes" "testing" ) func TestHello(t *testing.T) { expected := []byte("hello, world") stdout := new(bytes.Buffer) hello(stdout) if bytes.Compare(stdout.Bytes(), expected) != 0 { t.Fatal("greeting is not matched") } }
このテストは無事通ります.
[実行結果]
$ go test PASS ok _/C_/msys64/home/twinbird/test 0.128s
バイトストリームを構造体へ変換する(encoding/binaryを使う)
プログラムでデータを扱うとなるとやっぱり構造体でまとめたいですね.
一々バイトストリームから構造体へ変換する処理を書くのは面倒ですが,
golangにはいい感じにしてくれるパッケージがあります.
package main import ( "bytes" "fmt" "encoding/binary" ) type Beef struct { One byte Two byte Three byte Four byte } func main() { b := []byte{0xDe, 0xaD, 0xBe, 0xeF} buf := bytes.NewBuffer(b) var beef Beef binary.Read(buf, binary.LittleEndian, &beef) fmt.Printf("%x\n", beef.One) fmt.Printf("%x\n", beef.Two) fmt.Printf("%x\n", beef.Three) fmt.Printf("%x\n", beef.Four) }
[実行結果]
$ ./test.exe de ad be ef
読み出しだけ書いてみましたが, 書き出しもあります.
注意点としては
- 構造体のメンバはちゃんとエクスポートしておかないとダメ
- 渡すのは構造体へのポインタ
というところくらいだと思います.
エンディアンも指定出来て良いです.