Goならわかるシステムプログラミング2版3章読書メモ
Goならわかるシステムプログラミング2版3章読書メモ
io.Reader
バッファの管理をしつつ、何度もRead()
を呼んでデータを最後まで読み込む必要があり、少し面倒くさい。
コードのイメージはこんな感じ(読み込みが1024バイト超えたらどうするんだとか色々ツッコミどころがあるが…完全なコードはio.ReadAll()
を見たほうがよい)
// 1024バイトのバッファを作る
buffer := make([[byte, 1024)
for {
size, err := r.Read(buffer)
if err == io.EOF {
break
}
}
これを毎回書くのは大変なので、golangでは読み込み周りに補助機能が多く用意されている。
Read()
のブロック
golangのRead()
は入力待ちでブロックしてしまう。タイムアウトも無いので入力があるまで完全に停止してしまう。他のプログラミング言語だとノンブロックなAPIが用意されているが、golangの場合は並行処理機構を使うことでブロッキングを回避する。groutineで読み込んでチャネルを通して入力を処理するようにするのが一般的。
ファイル入力
os.Create()
とos.Open()
は内部的にはos.OpenFile()
という同じ関数のエイリアスになっていて、同じシステムコールが呼ばれている。
bufio.Reader
でnet.Conn
をラップする?
P42の3.4.3の以下の記述が引っかかった。
HTTPのレスポンスをパースする
http.ReadResponse()
関数が用意されています。この関数にbufio.Reader
でラップしたnet.Conn
を渡すと、http.Response
構造体のオブジェクトが返されます。
bufio.Reader
でnet.Conn
をラップする必要がよくわからなかったので調べてみる。
bufio.Reader
とは
バッファリングのためのio.Reader
の実装だとコードコメントにある。
-
Read()
に渡されたp
よりも内部バッファbuf
のサイズが小さければ直接p
に書き込む-
len(p) >= len(buf)
ならわざわざ内部バッファを使う必要がない
-
-
p
よりもbuf
のサイズが大きければbuf
で読み込んでからp
分だけコピーする-
len(p) < len(buf)
ならbuf
で読み取った方がたくさん読み込める
-
bufio.Reader
をnet.Conn
でラップする理由
bufio.Reader.Read()
の実装が役に立つのは、データソースから読み込む能力
> データを処理する能力
の場合なのかなあと思った。データソースからはたくさん読み込めるけれど、データを処理するのはより小さい単位でやらなければならない。しかし、データを処理する単位でデータソースから読み込んでいてはIOが多すぎる。というときにbufio.Reader
がうまくそれらの都合を吸収してくれる。
net.Conn
から読み込んだデータをどう処理するかはアプリケーションの都合によるのだと思うから、上のような差異が発生したとしてもうまくやれるようにbufio.Reader
でラップしておくと問題が起こりづらいということなのかなと思った。
PNGファイルの分析
3.5.3のreadChunks()
で引っかかったので詳細に読む。PNGのバイナリーフォーマットは
- 長さ: 4バイト
- 種類: 4バイト
- データ: 「長さ」バイト
- CRC: 4バイト
なので、PNGの各チャンクの概要を把握する手順としては以下になる。
- データの「長さ」を手に入れるために、チャンクの先頭でBigEndianとしてint32としてバイナリを解釈する
var length int32
err := binary.Read(file, binary.BigEndian, &length)
- 次のチャンク全体をひとかたまりにする
本書ではint64(length)+12となっているが、長さ、種類、データ、CRCをまとめたチャンクサイズになっている。
chunks = append(chunks, io.NewSectionReader(file, offset, 4+int64(length)+4+4)
- 次のチャンクの先頭まで移動する
binary.Read()
によって現在位置が「長さ」を読み終えたところになっている。なぜ現在位置が変わっているのかわからなくてデバッグして、binary.Read()
が動かしているということに気づいた。
というわけで、チャンク名(4byte)+データ長+CRC(4byte)先に移動すれば次のチャンクの先頭になる。
offset, _ = file.Seek(4+int64(length)+4, 1)
レナ・ソーダバーグさん
レナ・ソーダバーグさんの画像をサンプルに使っていましたが、その後、ご本人が技術分野での利用もモデルと同じように引退したいという表明をされたため、今回の改訂でサンプルを差し替えています。https://www.losinglena.com
知らなかった。署名しようと思ったけど、SSL証明書も切れているしFacebookのログイン周りのコードもメンテされてないみたいだ。
テキスト解析用のioReader関連機能
3.6.1のテキスト処理やや3.6.3のCSVの例を見ると、strings.Reader
をそのまま使うことは少なくて、より高機能なbufio.Reader
やcsv.Reader
で更に包んであげるという感じなのかなと思った。
冗長だし最初から高機能なio.Reader
の実装に文字列を渡せばいいのに…とも思ったが、一度シンプルなio.Reader
でラップしておくことで、高機能なio.Reader
の再利用性が上がっているのかなあと思う。文字列にしか使えないよりもio.Reader
に対して扱えた方が、先の例で見たようなHTTP経由のリクエストに対して使えたりするという利点がありそう。
Q3.1
flagを使ってコマンドライン引数でファイル名を取得して、古いファイルをos.Open()
、新しいファイルをos.Create()
してio.Copy()
で繋ぐ。
Q3.2
rand.Reader
は無限長のように振る舞うのでRead()
に指定したサイズ分埋めてくれる。
Q3.3
実際のファイルではなく、文字列
strings.Reader
を使ってzipファイルを作成するにはどうすればいいでしょうか。
これであってるのかなあ。問題文の意味としては実際のファイルを作らずに、ファイルとしてzipに含めるにはどうしたらいいかってことだと思うんだけど。
zipWriter.Create()
でファイルを仮想的に作ってそのio.Writer
にio.Copy()
でstrings.Reader
からファイルの中身を書き込めばいいのかなあと思いました。
Q3.4
Q3.3でzip.NewWriter()
にos.File
を渡していたところをhttp.ResponseWriter
にすれば良さそう。
Q3.5
io.Copy()
と本章で紹介した構造体のどれかを使って、
という記述が気になったけど、io.LimitReader()
を使えば間接的にio.LimitedReader
構造体を使ったことになる…よね?
Q3.6
与えられている文字列から必要な文字を切り出していって、io.MultiReader()
でまとめて流し込めばいいのかな。
Discussion