yikegaya’s blog

仕事関連(Webエンジニア)と資産運用について書いてます

「Goならわかるシステムプログラミング」を読み始めた

「Goならわかるシステムプログラミング」読み始めた。とりあえず1-3章まで読んだのでメモ。

Goならわかるシステムプログラミング(PDF版のみ) – 技術書出版と販売のラムダノート

Kindleで欲しかったんだけどなかったんで公式サイトからPDF形式を購入してみた。

1章

とりあえずGoやVSCodeのセットアップの説明から入って、そこからVSCodeにGoの拡張機能を入れてVSCodeのデバッガを使ってローレベルのコードを追っていく。

Hello Worldのコードを書いてVSCodeのbreakpointを貼ってデバッガを実行してStep IntoするとPrintlnメソッドの実装を確認できる。そこからさらにPrintlnの内部で使っているFprintlnの実装を読んで、、と進んでどんどんOSに近いレイヤーの実装を読むことができると言う内容。すごい。

func main() {
    fmt.Println("Hello World!")
}

func Println(a ...interface{}) (n int, err error) {
    return Fprintln(os.Stdout, a...)
}

func Fprintln(w io.Writer, a ...interface{}) (n int, err error) {
    p := newPrinter()
    p.doPrintln(a)
    n, err = w.Write(p.buf)
    p.free()
    return
}

みたいな感じ。あとVS codeで関数にカーソル当ててF12でコードジャンプできるらしい。これも知らなかった。

2章

2-4章はOSを操作するための抽象化レイヤーの話で、2章がio.Write、3章がio.Reader、4章がchannelの話。

2章はデータの入出力がio.Writeというインターフェイスで抽象化されていると言う話で、まずGo言語の構造体とインターフェイスの説明から入って、io.Writerを利用しているGoの構造体の説明が続く。

インターフェイスJavaにもあったけどそれと同じものだと考えて良いらしい。

// net.Connがio.Readerインタフェースでもあることを利用して、サーバーから返ってきたレスポンスを io.Copy を使って画面に出力
func main() {
    conn, err := net.Dial("tcp", "ascii.jp:80")
    if err != nil {
        panic(err)
    }
    io.WriteString(conn, "GET / HTTP/1.0/r/nHost: ascii.jp\r\nHost: ascii.jp\r\n\r\n")
    io.Copy(os.Stdout, conn)
}

// 複数の io.Writerを受け取り、それらすべてに対して、書き込まれた内容を同時に書き込む
func main() {
    file, err := os.Create("multiwriter.txt")
    if err != nil {
        panic(err)
    }
    writer := io.MultiWriter(file, os.Stdout)
    io.WriteString(writer, "io.MultiWriter example\n")
}
// 書き込まれたデータをgzip圧縮して、あらかじめ渡されていたos.Fileに中継
func main() {
    file, err := os.Create("test.txt.gz")
    if err != nil {
        panic(err)
    }
    writer := gzip.NewWriter(file)
    writer.Header.Name = "test.txt"
    io.WriteString(writer, "gzip.Writer example\n")  
    writer.Close()
}

など。io.Writerというインターフェイスに「何ができるか」を定義して、いろんな構造体でデータの入出力を行うメソッドを定義するときに入出力の型がio.Writerの内容を満たしているかを確認していく。

ioutil.WriteFile()とかhttp.Get()もio.Writeが隠蔽されていてデータの入出力を行う関数を使う時はio.Writerやio.Readerを引数に持つように作るみたい。

3章

io.Readerの話。2章の内容の読み込み版だけど画像ファイル(png)、文字列、CSVファイルなどを読み込んでいく。

画像とかバイナリとか普段Web開発で触らないレイヤーの読み込みは楽しいかな。。

// バイナリの読み込み
func main() {
    data := []byte{0x0, 0x0, 0x27, 0x10}
    var i int32
    binary.Read(bytes.NewReader(data), binary.BigEndian, &i)
    fmt.Printf("data: %d\n", i)
}

// 画像読み込み
func dumpChunk(chunk io.Reader) {
    var length int32
    binary.Read(chunk, binary.BigEndian, &length)
    buffer := make([]byte, 4)
    chunk.Read(buffer)
    fmt.Printf("chunk '%v' (%d bytes)\n", string(buffer), length)
}

func readChunk(file *os.File) []io.Reader {
    var chunks []io.Reader

    file.Seek(8, 0)
    var offset int64 = 8

    for {
        var length int32
        err := binary.Read(file, binary.BigEndian, &length)
        if err == io.EOF {
            break
        }
        chunks = append(chunks, io.NewSectionReader(file, offset, int64(length)+12))
        offset, _ = file.Seek(int64(length+8), 1)
    }
    return chunks
}

func main() {
    file, err := os.Open("lena.png")
    if err != nil {
        panic(err)
    }
    defer file.Close()
    chunks := readChunk(file)
    for _, chunk := range chunks {
        dumpChunk(chunk)
    }
}

とりあえずここで区切り