Golang 的 io package 包含 3 个文件 io.go, multi.go, pipe.go, 其中最主要的时 io.go。
当我们打开 io.go 的源码后,发现这个文件里面定义了大量的接口,实际上,io 包的作用就是如此 - 定义基本的 Read / Write inteface,而把具体的实现交给其他 package,比如 strings
package 中就专门实现了 reader/writer,在后面的文章中再分析 strings 包。
接下来就看看 io 包中到底包含了哪些东西。
io.go
首先时定义了 4 个基础操作,读,写,关闭,seek
type Reader interface {
Read(p []byte) (n int, err error)
}
type Writer interface {
Write(p []byte) (n int, err error)
}
type Closer interface {
Close() error
}
type Seeker interface {
Seek(offset int64, whence int) (int64, error)
}
基于这 4 个基础 interface,两两组合,有扩展了下面几个 interface
- ReadWriter
- ReadCloser
- WriteCloser
- ReadWriteCloser
- ReadSeeker
- WriteSeeker
- ReadWriteSeeker
这块内容直接看源码即可,不再赘述。
如果使用 Goland IDE 的话,打开 struct 视图就能很清楚的看到有哪些接口,那些 public 函数。
io.go 中定义了哪些结构体呢 ? 有 2 个
- SectionReader
- LimitReader
这两个结构体的实现也非常简单。
multi.go
这个文件中主要实现了 2 个 私有结构体及其方法: multiReader
和 multiWriter
, 并提供了 2 个共有函数来创建它们。
- func MultiWriter(writers …Writer) Writer
- func MultiReader(readers …Reader) Reader
顾名思义,就是用户可以一次传入多个 reader,multiReader
帮你逐个读取。 整体的实现也非常简单:
type multiReader struct {
readers []Reader
}
func (mr *multiReader) Read(p []byte) (n int, err error) {
for len(mr.readers) > 0 {
// Optimization to flatten nested multiReaders (Issue 13558).
if len(mr.readers) == 1 {
if r, ok := mr.readers[0].(*multiReader); ok {
mr.readers = r.readers
continue
}
}
n, err = mr.readers[0].Read(p)
if err == EOF {
// Use eofReader instead of nil to avoid nil panic
// after performing flatten (Issue 18232).
mr.readers[0] = eofReader{} // permit earlier GC
mr.readers = mr.readers[1:]
}
if n > 0 || err != EOF {
if err == EOF && len(mr.readers) > 0 {
// Don't return EOF yet. More readers remain.
err = nil
}
return
}
}
return 0, EOF
}
func MultiReader(readers ...Reader) Reader {
r := make([]Reader, len(readers))
copy(r, readers)
return &multiReader{r}
}
注意到其中的注释 Issue 18232,这里提到了一个非常有意思的 corner case , 通过阅读这个 fix 的测试用例就能明白在何种情况下可以触发 bug 。
解决方法就是不要把 mr.readers[0] = nil
, 而是用一个 eofReader{}
, 它的 Read 函数返回 EOF,这样用户再次调用时返回 EOF 而不是 nil 。
这样的 bug 以及 fix 的方法对我们非常有学习的意义,既能学到随便赋值 nil 带来的严重后果,又能学到该怎么优雅的 fix ( eofReader )
pipe.go
这个文件是实现了一个 pipe,当调用 func Pipe() (*PipeReader, *PipeWriter)
返回一个 PipeReader 和 PipeWriter 作为管道的读写两端。
不过感觉 pipe 能用到的场景很少,从 pipe_test.go 文件中也能看出,使用方式是开启独立的 reader/writer goroutine。
那么,为什么不用 channel 来让 goroutine 之间通信呢? 似乎 channel 才是 golang 中更加 native 的方法。 关于 pipe 的分析,可以查阅参考资料 1 。