io¶
io パッケージの役割は以下の2つ
I/Oプリミティブを抽象化する共通のインターフェース(osのファイルディスクリプタのioとか、バッファのioとか)
関連するI/Oのラッパー
注釈
必ずしもGroutineセーフではない
目次
io.Reader (interface)¶
シグネチャ¶
io.Readerは以下のシグネチャを持つReadメソッドを定義しているインターフェースです。p byte[]
は引数の読み取りした内容を一時的に格納しておくバッファです。
type Reader interface {
Read(p []byte) (n int, err error)
}
ちなみに Read
は非常にプリミティブなメソッドなので、通常直接このメソッドを扱うことは少なく、ラッパーの高級関数 (ioutil.ReadAll, ioutil.ReadFile, bufio.Scanner
) を使うことが多いのではないでしょうか。
仕様¶
GoDoc のパッケージコメントから特徴(想定している仕様)を見てみます。
最大
len(p)
バイトを読み取り、読み取ったバイト数 n とエラーを返却n < len(p)
だったとしてもバッファ p をすべて使っている場合がある非nilのエラーを返すか、次の呼び出しでエラーを返す(その時はn=0)
(0, nil)
を返却することは非推奨。(0, nil)
はEOFを示さない。終端まで読んだときは(0, EOF)
を返す
要は バイト列を読み取るためのインターフェース です。読み取るものは、標準入力でも、ファイルでも、ソケット、バッファでもなんでもよいです。
実装¶
例えば os.File
や bytes.Buffer
構造体は Read(p []byte) (n int, err error)
メソッドを実装しており io.Reader
インターフェースを満たしています。(同時に io.Writer も満たしていることが多い)
type File struct {
*file // os specific
}
// ...
// Readメソッドを実装しているので、io.Readerインターフェースを満たしている
// ファイルから len(b) バイトを読み出す
func (f *File) Read(b []byte) (n int, err error) {
if err := f.checkValid("read"); err != nil {
return 0, err
}
// 実際の読み込み処理は各OSのファイルのreadに移譲
n, e := f.read(b)
return n, f.wrapErr("read", e)
}
type Buffer struct {
buf []byte // データ buf[off : len(buf)] などと参照される
off int // オフセット
lastRead readOp
}
// ...
// バッファから len(p) バイト読み出すか、バッファが空になるまで読む
func (b *Buffer) Read(p []byte) (n int, err error) {
b.lastRead = opInvalid
if b.empty() {
// Buffer is empty, reset to recover space.
b.Reset()
if len(p) == 0 {
return 0, nil
}
return 0, io.EOF
}
n = copy(p, b.buf[b.off:])
b.off += n
if n > 0 {
b.lastRead = opRead
}
return n, nil
}
注釈
ちなみにメソッドのレシーバがポインタ型(*T)だったけど、それで大丈夫?呼び出すときは型 T から呼び出しても大丈夫なの?という疑問があるかも知れません。私はそう思いました。
結論から言うと大丈夫です。というよりもポインタに変換されて呼び出されます。まずGoの仕様として、あるタイプTのポインタ型(*T)として宣言されているメソッドは、レシーバ *T として宣言されたメソッドと T で宣言されたメソッドを含みます。また型 T の変数 t で宣言されたオブジェクトが t.x() のメソッドを呼び出すときに &t のメソッドとして x が含まれている場合は (&t).x() がGoのコンパイラによって変換されて呼び出されます。
https://golang.org/ref/spec#Method_sets https://golang.org/ref/spec#Calls
実際どんな感じで io.Reader
の Read
メソッドが呼ばれているか ioutil/ioutil.go
の ReadFile
メソッドを見てみます。ioutil.ReadFile
はファイルからデータを読み取るときに使います。
// ファイルからデータを読み出す
// すべて読んだ場合は EOF error は返さず nil を返す
func ReadFile(filename string) ([]byte, error) {
f, err := os.Open(filename)
if err != nil {
return nil, err
}
defer f.Close()
// ファイルからファイルサイズを取得するが正確でないことがある為
// 512 バイトを余分に確保しておく。最低 512 バイト確保される
var n int64 = bytes.MinRead
if fi, err := f.Stat(); err == nil {
if size := fi.Size() + bytes.MinRead; size > n {
n = size
}
}
return readAll(f, n)
}
// ...
// io.Reader から EOF やエラーになるまでデータを読み取る
func readAll(r io.Reader, capacity int64) (b []byte, err error) {
var buf bytes.Buffer
// バッファオーバーフローした場合のみpanicをrecoverしてbytes.ErrTooLargeのエラーとして返す
// それ以外は panic を起こす
defer func() {
// 滅多に見ない recover() 関数
e := recover()
if e == nil {
return
}
// panicが発生したエラーの型をチェックして、エラー型だった場合は、値を見て
// bytes.ErrTooLarge("bytes.Buffer: too large")の場合はエラーとして回復
if panicErr, ok := e.(error); ok && panicErr == bytes.ErrTooLarge {
err = panicErr
} else {
panic(e)
}
}()
if int64(int(capacity)) == capacity {
buf.Grow(int(capacity))
}
// 内部的には bytes の ReadFrom が呼び出される
_, err = buf.ReadFrom(r)
return buf.Bytes(), err
}
// 最小のバッファサイズ(512バイト)
const MinRead = 512
// io.Reader から EOF までデータを読み取り、バッファに追加する
// 必要に応じてバッファを拡張する
// バッファが大きくなりすぎる場合は ErrTooLarge を返す
func (b *Buffer) ReadFrom(r io.Reader) (n int64, err error) {
b.lastRead = opInvalid
// forループで終了条件 (io.EOF or error) に達するまで処理
for {
// *Bufferで保持している内部のバッファを割り当てるだけで十分であれば、拡張したスライスを返す
// 足りなければ *buffer が保持しているバッファを元の大きさの約2倍に拡張する
i := b.grow(MinRead)
b.buf = b.buf[:i]
// io.Reader を満たしている構造体の Read メソッドを呼び出す
m, e := r.Read(b.buf[i:cap(b.buf)])
if m < 0 {
panic(errNegativeRead)
}
b.buf = b.buf[:i+m]
n += int64(m)
if e == io.EOF {
return n, nil // e is EOF, so return nil explicitly
}
if e != nil {
return n, e
}
}
}
注釈
os.Openやos.Createで生成したos.File構造体はio.Readerを満たしており、Openした変数をそのままioutil.ReadAllに渡せるので、わざわざioutil.ReadFileがあるのはなんでだろうと思います。ファイルの場合は stat()
などによりおおよそのファイルサイズがわかるため、バッファ領域の確保をより正確にするためと想定しています。
インターフェースを扱う¶
個人的に良い実装だな、と思うのは ReadAll
のシグネチャが以下のように io.Reader
を受け取るようになっていることです。readAll
や bytes.ReadFrom
, bufio.NewReader
も同様。
ReadAll(r io.Reader) ([]byte, error)
readAll(r io.Reader, capacity int64) (b []byte, err error)
ReadFrom(r io.Reader) (n int64, err error)
NewReader(rd io.Reader) *Reader // bufio.Reader も io.Reader を満たしている
ReadAll
メソッドは r io.Reader
とインターフェースを引数に取るようになっています。これによって読み出す対象が何であるか気にする必要がなく io.Reader
インターフェースを満たす構造体であれば何でも受け取ることできます。ファイルを読みたい場合は ReadFile
のようにラッパーとして実装すればよいだけでOKです。
注釈
インターフェースを使った汎用的なデザインになっているのが良いと思っているのでGo特有というわけではない気がします。JavaだとInterfaceとかAbstractクラスとか使って実装する気がします。
上記のメソッド/関数の他にも、例えば json を扱う際の json.NewDecoder
は以下のようになっていますし、独自にI/Oを扱う場合は io.Reader
を受けとるようにすればよいのではないでしょうか。
func NewDecoder(r io.Reader) *Decoder {
return &Decoder{r: r}
}
独自の io.Reader を作る¶
独自の io.Reader インターフェースを実装した myReader 構造体を作ってみます。
type myReader struct {
content []byte // 読み出す対象のバイト列
position int // 次に読むオフセット
}
func (r *myReader) Read(buf []byte) (int, error) {
remainingBytes := len(r.content) - r.position
n := min(remainingBytes, len(buf))
if n == 0 {
return 0, io.EOF
}
// copyはビルトイン関数
copy(buf[:n], r.content[r.position:r.position+n])
r.position += n
return n, nil
}
func min(a int, b int) int {
if a < b {
return a
}
return b
}
そうすると以下のように ioutil.ReadAll
にわたすことができます。io.Reader
インターフェースを満たすだけで、io.Reader
を受け取る、ありとあらゆる関数を利用することができます。(以下のサンプル実装の場合はうれしみがないですが)
func main() {
reader := &myReader{content: []byte("this is the stuff I'm reading")}
bytes, err := ioutil.ReadAll(reader)
if err != nil {
log.Fatal(err)
}
fmt.Println(string(bytes))
}
// this is the stuff I'm reading
https://play.golang.org/p/xA1UdgJwwdv
注釈
ちなみにファイル終端の EOF は以下のように実装されていました。たしかに error として定義されています。
var EOF = errors.New("EOF")
io.Writer (interface)¶
シグネチャ¶
io.Writer
も io.Reader
に似ているインターフェースで以下の Write
メソッドだけを持っているインターフェースです。なので、 Write
メソッドを満たしていれば io.Writer
になれます。
type Writer interface {
Write(p []byte) (n int, err error)
}
仕様¶
p
からlen(p)
バイトを書き込み、書き込んだバイト数とエラーを返却するn < len(p)
の場合は非nilのエラーの返却する必要がある
io.Reader
と比較すると仕様がシンプルです。
実装¶
インターフェースを満たしている構造体の例を見てみます。例えば os.File
は以下のように実装しています。
func (f *File) Write(b []byte) (n int, err error) {
if err := f.checkValid("write"); err != nil {
return 0, err
}
n, e := f.write(b)
if n < 0 {
n = 0
}
if n != len(b) {
err = io.ErrShortWrite
}
epipecheck(f, e)
if e != nil {
err = f.wrapErr("write", e)
}
return n, err
}
また bufio.Buffer
では以下のように実装しています。
func (b *Buffer) Write(p []byte) (n int, err error) {
b.lastRead = opInvalid
m, ok := b.tryGrowByReslice(len(p))
if !ok {
m = b.grow(len(p))
}
return copy(b.buf[m:], p), nil
}
実際どんな感じで io.Writer
の Write
メソッドが呼ばれているか見てみます。
func WriteString(w Writer, s string) (n int, err error) {
if sw, ok := w.(StringWriter); ok {
return sw.WriteString(s)
}
// 呼び出し元の構造体で実装している Write メソッドを呼び出す
return w.Write([]byte(s))
}
func WriteFile(filename string, data []byte, perm os.FileMode) error {
f, err := os.OpenFile(filename, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, perm)
if err != nil {
return err
}
// os.File構造体のWriteを呼び出す
n, err := f.Write(data)
if err == nil && n < len(data) {
err = io.ErrShortWrite
}
if err1 := f.Close(); err == nil {
err = err1
}
return err
}
io.Copy (func)¶
インターフェースではないですが、io.Copy
も io パッケージの主要なメソッドの一つだと思うので取り上げます。
仕様¶
src から EOF に到達するかエラーが発生するまで dst にコピー
コピーしたバイトするとエラーを返す
実装¶
func Copy(dst Writer, src Reader) (written int64, err error) {
return copyBuffer(dst, src, nil)
}
func CopyBuffer(dst Writer, src Reader, buf []byte) (written int64, err error) {
if buf != nil && len(buf) == 0 {
panic("empty buffer in io.CopyBuffer")
}
return copyBuffer(dst, src, buf)
}
func copyBuffer(dst Writer, src Reader, buf []byte) (written int64, err error) {
// If the reader has a WriteTo method, use it to do the copy.
// Avoids an allocation and a copy.
if wt, ok := src.(WriterTo); ok {
return wt.WriteTo(dst)
}
// Similarly, if the writer has a ReadFrom method, use it to do the copy.
if rt, ok := dst.(ReaderFrom); ok {
return rt.ReadFrom(src)
}
if buf == nil {
// デフォルトでは 32KB をバッファとして確保
size := 32 * 1024
if l, ok := src.(*LimitedReader); ok && int64(size) > l.N {
if l.N < 1 {
size = 1
} else {
size = int(l.N)
}
}
buf = make([]byte, size)
}
for {
nr, er := src.Read(buf)
if nr > 0 {
nw, ew := dst.Write(buf[0:nr])
if nw > 0 {
written += int64(nw)
}
if ew != nil {
err = ew
break
}
if nr != nw {
err = ErrShortWrite
break
}
}
if er != nil {
if er != EOF {
err = er
}
break
}
}
return written, err
}
注釈
Go Conference で聞いた高度なテクニックですが、sync.Pool
でバッファを明示的に指定して io.Copy から io.CopyBuffer にしたところ、メモリ使用量が削減したという話もあります。
https://github.com/src-d/go-git/pull/1179
どちらかというと sync.Pool の性質(メモリに割り当てられている不要なアイテムをキャッシュし、後で再利用することで、 GC の負荷を下げる)を利用しているテクということだと思います。
copyするバイト数がわかっていれば、CopyN
で明示的にコピーするバイト数を指定することもできます。io.Copy
のラッパー。
func CopyN(dst Writer, src Reader, n int64) (written int64, err error) {
// LimitReader は src の io.Reader から n バイトだけ読むこむ io.Reader を返却する関数
written, err = Copy(dst, LimitReader(src, n))
if written == n {
return n, nil
}
if written < n && err == nil {
err = EOF
}
return
}
io.MultiWriter (func)¶
io.MultiWriter
は io.Writer
のスライスを内部で保持していて、それぞれの io.Writer
の Write
メソッドを呼んでいました。デザインパターンでいうところのデコレータパターンらしいです。
func MultiWriter(writers ...Writer) Writer {
allWriters := make([]Writer, 0, len(writers))
for _, w := range writers {
if mw, ok := w.(*multiWriter); ok {
allWriters = append(allWriters, mw.writers...)
} else {
allWriters = append(allWriters, w)
}
}
return &multiWriter{allWriters}
}
func (t *multiWriter) Write(p []byte) (n int, err error) {
for _, w := range t.writers {
n, err = w.Write(p)
if err != nil {
return
}
if n != len(p) {
err = ErrShortWrite
return
}
}
return len(p), nil
}
io.Pipe (func)¶
インメモリで io.Writer と io.Reader を同期的につなげるパイプの機能。内部でバッファリング されない のが特徴。シーケンシャルに読み書きされる。
func Pipe() (*PipeReader, *PipeWriter) {
p := &pipe{
wrCh: make(chan []byte),
rdCh: make(chan int),
done: make(chan struct{}),
}
return &PipeReader{p}, &PipeWriter{p}
}
type pipe struct {
wrMu sync.Mutex // Serializes Write operations
wrCh chan []byte
rdCh chan int
once sync.Once // Protects closing done
done chan struct{}
rerr atomicError
werr atomicError
}
type multiWriter struct {
writers []Writer
}
type PipeWriter struct {
p *pipe
}
// 書き込みの処理は pipe に移譲
func (w *PipeWriter) Write(data []byte) (n int, err error) {
return w.p.Write(data)
}
func (p *pipe) Write(b []byte) (n int, err error) {
select {
// done チャネルがクローズされている場合
case <-p.done:
return 0, p.writeCloseError()
// そうでない場合はmutexでロックを取得
default:
p.wrMu.Lock()
defer p.wrMu.Unlock()
}
// このスコープでの once の役割がよくわからない
for once := true; once || len(b) > 0; once = false {
select {
// バイトスライスをチャネルに送信
case p.wrCh <- b:
// reader のチャネルから読み込んだint値を受信
nw := <-p.rdCh
// reader が読み込んだ文字数バイトスライスをすすめる
b = b[nw:]
n += nw
case <-p.done:
return n, p.writeCloseError()
}
}
// 読んだ文字数を返却
// n は名前付き変数の戻り値 (参考: https://golang.org/doc/effective_go.html#named-results)
return n, nil
}
// クローズした場合はこれが呼ばれる
func (p *pipe) CloseWrite(err error) error {
if err == nil {
err = EOF
}
p.werr.Store(err)
// done チャネルのクローズ、sync.Onceなので複数のゴルーチンから同時に呼ばれたとしても一回きりしか呼ばれない
p.once.Do(func() { close(p.done) })
return nil
}