読者です 読者をやめる 読者になる 読者になる

write ahead log

ロールフォワード用

golangでバイナリを操作する

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

読み出しだけ書いてみましたが, 書き出しもあります.

注意点としては

  • 構造体のメンバはちゃんとエクスポートしておかないとダメ
  • 渡すのは構造体へのポインタ

というところくらいだと思います.

エンディアンも指定出来て良いです.