嘛,好久Go
相关的文章的,这期我们来看一看 Go
的 io
库。
io
是 Go
中非常重要,但同时也非常简单的部分。它抽象出了最基本的 Reader
和 Writer
,并实现了 MultiWriter
和 Pipe
。代码很简单,很简单,但是却是一切的基础。正是因为它非常简单,这篇文章才能带上源码讲(
godoc
固然方便,但它也有它的缺点,就是取消了文件之间的障碍,反而增加了整理理解的难度,并且打乱了阅读的思路。因此也只能作为一个字典式的工具存在,难以代替源码阅读本身的意义。
(或许 Go
的这几篇文章也能成为一个系列呢,谁知道呢(
ToC
Reader
与 Writer
饼一口一口吃,我们先来看最基本的两个 interface
:Reader
和 Writer
。
Reader
和 Writer
是相对数据的概念,因此 Read 就意味着从数据中读,而 Write 就是向数据中写。因此在下面我们会发现,Reader
需要提供一个 []byte
作为输入,它就是用来承载从数据中读出的内容的,执行完成后数组中内容发生改变;而 Write 则恰恰完全相反,它虽然也许要提供一个 []byte
,但这个数组是用来写入的,执行完成后数组中的内容不会改变。
在看详细的内容之前,我们先来看一看整个文件开头的注释。行号代表的是在 io.go
中的位置,下同。
这里值得我们注意的是最后一段:io
对并行安全性不作保证。这很符合常识,不是吗(
下面我们来看相对详细的内容。
Reader
可以看到,这个接口只有一个 Read
函数,参数是上面我们提到的 []byte
,返回值是 n
和 err
。err
很好理解,那 n
又是什么呢?
这时候我们回去看上面的注释。注释就解释得很明白:
可以看到,注释中既有对使用者的说明,也有对实现者的实现建议。这里我们作为使用者,始终需要记住的一点就是:不要因为 err
就丢弃 n
。
这或许和我们平常使用的错误处理不同,这也是 Reader
的特点之一:无论是否出现错误,n
代表的内容是始终需要处理的。
Writer
同理,这里的 Writer 也需要遵守上面的一些规则。这里需要补充的是以下两条:
- 当
n < len(p)
时,err 必须返回 非 nil。 Write
禁止对p
作哪怕是暂时的任何修改。
Closer
与 Seeker
看完了最常用的两个,接下来来看看虽然没那么常用,但其实也很常用的两个接口:Closer
和 Seeker
。
Closer
Closer
是包含了 Close
方法的接口,基本用途正如其名。这里需要注意的只有一点:Close
的多次调用是未定义行为,遇到时请具体问题具体分析(指查对应的文档
Seeker
Seeker
相对来说稍微复杂那么一点点,但也很简单。offset
对应的是偏移,而 whence
代表的是三种可能:SeekStart
、SeekCurrent
和 SeekEnd
,代表 offset
是相对开头,当前位置还是结尾的。
<heimu>retime</heimu>
排列组合
在上四者都定义完之后,就是一大堆的排列组合了(准确说是组合),有:
- ReadWriter
- ReadCloser
- WriteCloser
- ReadWriteCloser
- ReadSeeker
- WriteSeeker
- ReadWriteSeeker
ReadFrom
与 WriteTo
接下来的这一对接口就相对比较靠近应用层了。ReadFrom
是从 Reader
中读取所有信息到自身,而 WriteTo
则是将自身所有信息写入 Writer
。当实现了下面这两个接口时,io.Copy
会优先使用这两个接口。我们来看:
ReadFrom
ReadFrom
函数会持续尝试 Read
,直到 EOF
为止。这里需要注意的是:当 r.Read
返回 非 EOF
的错误时,错误应一并返回。
WriteTo
和 ReadFrom
类似,WriteTo
则是尽可能地向 Writer
中写入数据,返回 n
表示写入的字节数,err
表示错误。同理,当 w.Write
产生错误时,WriteTo
也要把这个错误返回。
结语
除了上文中描述的内容,io
中还包含了一些对常用类型的包装,如 StringWriter
、Byte
系列的 Reader
、Writer
和 Scanner
,以及结合了 Read
和 Write
的 Pipe
。使用者可以结合实际情况去实现对应的 interface
,赋予 struct
更多的 I/O
能力。
嘛,就是这样(