問題を解いて学ぶio.Writerとio.Reader(Go)
はじめに
Go ならわかるシステムプログラミングを読みました。
- 作者:渋川 よしき
- 発売日: 2017/10/23
- メディア: 単行本(ソフトカバー)
僕は本で読みましたが、元々は Web で連載していたみたいで無料で読むことができます。(本の方がコンテンツは充実しています)
この本で学んだことを小出しにアウトプットしていきたいと思います。
まずは、I/O 関係の基本の io.Writer
と io.Reader
を紹介します。
io.Writer とは
全ての Write 処理の基本となる インターフェース です。 Go のインターフェースは、構造体などの具象型が満たすべき仕様(持つべきメソッド)を表現するための言語機能です。 OS がカーネルのレイヤーでさまざまな入出力をファイルと同様に扱えるファイルディスクリプタという仕組みがあるのですが、このインターフェースではそのような共通化の仕組みを言語レベルで行っており、OS による差異も吸収しています。
type Writer interface { Write(p []byte) (n int, err error) }
例えば、io.Writer
インターフェースで言えば、引数[]byte
、返り値 int, error
を持つメソッドWrite
を実装する構造体であれば、io.Writer
インターフェースを満たしていると言えます。
os.Stdout
や os.File
は io.Writer
インターフェースを満たしています。
io.Reader とは
全ての Read 処理の基本となる インターフェース です。
type Reader interface { Read(p []byte) (n int, err error) }
p := make([]byte, 1024) // メモリ確保 n, err := file.Read(p) // メモリに格納, 読み込みサイズを返す // こちらが使われることが多い buffer, err := ioutil.ReadAll(reader) // メモリを気にせず全て読む便利メソッド
問題
ASCII.jp:Goならわかるシステムプログラミング より引用します。 連載を読みながらでないと隠れた文脈を理解できないので、実際に解くのはハードルが高いです。 解答を見て、雰囲気を理解するだけでも良いと思います。
ファイルのコピー
古いファイル(old.txt)を新しいファイル(new.txt)にコピーしてみましょう。
シェルでのcpコマンドのようなものです。
答え
package main import ( "io" "os" ) func main() { // エラー処理は省略 // のちにio.Readerとして使われるos.File oldFile, _ := os.Open("old.txt") // 終了したらファイルディスクリプタを閉じる defer oldFile.Close() // のちにio.Writerとして使われるos.File newFile, _ := os.Create("new.txt") // 終了したらファイルディスクリプタを閉じる defer newFile.Close() // oldFileからnewFileへコピー io.Copy(newFile, oldFile) }
io.Copy
はio.Reader
からio.Writer
へそのままデータを渡したい時に使います。
サイズを指定指定したいときは、io.CopyN
を使います。
zip ファイルをウェブサーバからダウンロード
zip ファイルの出力先は単なる io.Writer です。 ウェブサーバにブラウザでアクセスしたらファイルがダウンロードさ1れるようにしてみましょう。 この場合は、Content-Type ヘッダーを使ってファイルの種類が zip ファイルであることをブラウザに教えてあげる必要があります。
答え
package main import ( "archive/zip" "io" "net/http" "strings" ) func handler(w http.ResponseWriter, r *http.Request) { // エラー処理は省略 // ブラウザにzipファイルであることを伝える w.Header().Set("Content-Type", "application/zip") // ファイル名を指定 w.Header().Set("Content-Disposition", "attachment; filename-ascii_sample.zip") // 出力先のWriterをzip.NewWriterに渡すとzipファイル書き込み用の構造体ができる zipWriter := zip.NewWriter(w) // 終了したらファイルディスクリプタを閉じる defer zipWriter.Close() // zipファイルに含めるファイルを作成 a, _ := zipWriter.Create("a.txt") io.Copy(a, strings.NewReader("1つめのファイルのテキストです")) // zipファイルに含めるファイルを作成 b, _ := zipWriter.Create("b.txt") io.Copy(b, strings.NewReader("2つめのファイルのテキストです")) } func main() { http.HandleFunc("/", handler) http.ListenAndServe(":8080", nil) }
まとめ
私は、io.Writer
とio.Reader
はファイルディスクリプタを言語レベルで実現し、Go実行環境による差異を吸収し抽象化したものと捉えています。
ファイルディスクリプタとはOSがカーネルレイヤーで入出力を抽象化する仕組みであり、これによってファイルや標準入出力など外部入出力が異なっていても差異を吸収して同じように扱うことができます。
io.Writer
やio.Reader
は、[]byte
に変換せずストリームのように扱うことができるので今後また記事にまとめます。