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 个

  1. SectionReader
  2. LimitReader

这两个结构体的实现也非常简单。

multi.go

这个文件中主要实现了 2 个 私有结构体及其方法: multiReadermultiWriter, 并提供了 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 。

参考资料