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

write ahead log

ロールフォワード用

golangでバイナリを操作する

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

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

注意点としては

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

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

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

golangでfnvハッシュ関数を使う

golang

FNVハッシュ関数は64bit or 32bitでの出力を行うことができるハッシュ関数です.

SHAシリーズ(SHA-1など)やMDシリーズ(MD5など)などとは異なり, 衝突耐性よりも実装が効率的な事を重視しているっぽい.

逆にセキュリティなどを重視するところでは使っちゃダメですね.

サンプルコードをメモっとく.

package main

import (
    "fmt"
    "hash/fnv"
)

func hash32(message []byte) uint32 {
    h := fnv.New32()
    h.Write(message)
    sum := h.Sum32()
    return sum
}

func hash64(message []byte) uint64 {
    h := fnv.New64()
    h.Write(message)
    sum := h.Sum64()
    return sum
}

func main() {
    fmt.Println(hash32([]byte("Test Hashing")))
    fmt.Println(hash64([]byte("Test Hashing")))
}

ピアソンハッシュ関数(?)を書いてみた(golangで)

golang 作った/試した

子供が熱出して休暇もらったので, おかあさんといっしょを見ながらこれ書いてます.

わけあって8bit出力のハッシュ関数を探していたんですが, 流石に標準ではなさそうなので簡単でそこそこ良いものを探していました.
(32bitならFNVがありました.そのうち使い方をメモしておこうと思います.)

Wikipediaに行くと色々なハッシュ関数のリストがありました.

List of hash functions - wikipedia

8bit出力出来るものがあまりないのですが, 非暗号的ハッシュ関数の中にPearson hashingというものがあります.

恐らくピアソンハッシングと呼ぶのではないかと思います.(有名なんですかね?)

さらにWikipediaを辿ると詳しい説明が載っています.

Pearson hashing - Wikipedia

いやー, Wikipediaには頼りっぱなしです.寄付してよかったです.

拙い英語力で読む限り

  • 8bitレジスタしかない
  • マシンパワーがしょぼい
  • 暗号的である必要がない(意図的に衝突させられてもOK)

などといった限定条件下では効果的なハッシュ関数っぽいですね.

今回使いたかった用途にピッタリですし, サンプルもあったのでちょっとgolangで実装してみました.

大したコードでもないのでgistに入れました.

Pearson hashing sample by golang

実行結果はこちら

f:id:twinbird_htn:20170217230157p:plain

そういえばgistってライセンスどうなるんですかね.

まぁ責任は取れないですけど, ご自由にどうぞ.

Windows10のスタートアップフォルダの場所

Windows

毎回調べないとならなくなってきて鬱陶しいのでメモ.

Windows7と8でもたぶん同じ.

C:\Users\ [ユーザー名]\AppData\Roaming\Microsoft\Windows\Start Menu\Programs\Startup

golangで端末で使う単語帳を作った

作った/試した golang

作った.
学生の課題みたいですね.

github.com

これは何

端末上で使う単語帳です.

こんな感じのデータを用意すると

f:id:twinbird_htn:20170205072019p:plain

こんな感じになります.

f:id:twinbird_htn:20170205072120p:plain

実際にタイプしたり考えたりしないと覚えられないので, 作りました.

ブラウザ上でもよかったのですが, 個人的事情で端末上で.

例文や単語はどうするのか?

TSVで作っておいてこのプログラムに食べさせるのですが,
単語帳を作る過程が一番勉強になります(ぇ).

まぁ, 単語帳なんてそういうものでしょう.

本当はコミットメッセージの日・英対になっている例文が欲しいのですが,
用意するのに時間がかかりそうです.

golangでシグナルをハンドリングする

golang

golangでコマンド作ってもctrl + cとかで止めたくなるじゃないですか.

ちょっと調べた.

package main

import (
    "fmt"
    "os"
    "os/signal"
    "syscall"
)

func main() {
    sig_ch := make(chan os.Signal, 1)
    signal.Notify(sig_ch,
        os.Interrupt,
        syscall.SIGHUP,
        syscall.SIGINT,
        syscall.SIGTERM,
        syscall.SIGQUIT)

    exit_ch := make(chan int)
    go func() {
        for {
            s := <-sig_ch
            switch s {
            case os.Interrupt:
                // windowsはこれで頑張る
                fmt.Println("Interrupt")
                exit_ch <- 0
            case syscall.SIGHUP:
                fmt.Println("SIGHUP")
                exit_ch <- 0
            case syscall.SIGINT:
                fmt.Println("SIGINT")
                exit_ch <- 0
            case syscall.SIGTERM:
                fmt.Println("SIGTERM")
                exit_ch <- 0
            case syscall.SIGQUIT:
                fmt.Println("SIGQUIT")
                exit_ch <- 0
            default:
                fmt.Println("Other")
                exit_ch <- 1
            }
        }
    }()
    code := <-exit_ch
    os.Exit(code)
}

Windowsはちょっと別枠ですが, ちゃんと取り扱えるのはいいですね.

余談ですが, Windows + msys2だとうまく動かないのでいい方法が知りたいです.

golangでtsv(csv)を読む

golang

探せばいくらでも出てきそうだけど,メモしておく.

コード見たほうが早いと思うので.

package main

import (
    "encoding/csv"
    "fmt"
    "log"
    "strings"
)

func main() {
    // テスト用文字列
    str := "test\tテスト\nHello\tこんにちは"

    // CSVのReaderを用意
    r := csv.NewReader(strings.NewReader(str))

    // デリミタ(TSVなら\t, CSVなら,)設定
    r.Comma = '\t'

    // コメント設定(なんとコメント文字を指定できる!)
    r.Comment = '#'

    // 全部読みだす
    records, err := r.ReadAll()
    if err != nil {
        log.Fatal(err)
    }

    // 各行でループ
    for _, v := range records {
        // 1列目
        fmt.Print(v[0])

        fmt.Print(" | ")

        // 2列目
        fmt.Println(v[1])
    }
}

実行結果

$ ./tsv.exe
test | テスト
Hello | こんにちは

割と使いやすいライブラリと思う(クセがない)