原文链接
- Streaming IO in Go – Learning the Go Programming Language – Medium
以下为译文
在 Go 中,输入输出操作是通过能读能写的字节流数据模型来实现的。为此,io 包提供了 io.Reader 和 io.Writer 接口来进行输入输出操作,如下所示:
Go 附带了许多 API,这些 API 支持来自内存结构,文件,网络连接等资源的流式 IO。本文重点介绍如何自定义实现以及使用标准库中的 io.Reader 和 io.Writer接口创建能够传输流式数据的 Go 程序
io.Reader
由 io.Reader 接口表示的读取器将数据从某些源读取到缓冲区,可以像用水管输送水流一样来传送它,如下所示
对于要用作读取器的类型,它必须从接口 io.Reader 实现 Read(p [] byte)方法,如下所示:
type Reader interface {
Read(p []byte) (n int, err error)
}
Read() 方法的实现应返回读取的字节数或发生的错误。如果数据源已输出全部内容,则 Read 应返回 io.EOF
读取规则(补充)
在 Reddit 反馈之后,我决定添加有关读取规则的这一部分。读取器的行为取决于它的实现,但是你应该知道从读取器读取数据时, io.Reader 中的一些规则:
译者注:p 为缓冲区,n 为字节数
- 如果可能,Read() 将读取 len(p) 到 p
- 调用 Read() 后,返回的字节数 n 可能小于 len(p)
- 出错时,Read() 仍可在缓冲区 p 中返回 n 个字节。例如,从突然关闭的 TCP 套接字读取。取决于您的程序设计,您可以选择将字节保存在 p 中或重新尝试从 TCP 套接字中读取
- 当 Read() 读完所有可用数据时,读取器可能返回非零 n 和 err = io.EOF。尽管如此,您可以自己实现返回规则,如可以选择在流的末尾返回非零 n 和 err = nil。在这种情况下,任何后续读取必须返回 n = 0,err = io.EOF
- 最后,调用 Read() 返回 n = 0 和 err = nil 并不意味着 EOF,因为下一次调用 Read() 可能会返回更多数据
如您所见,直接从读取器读取流数据可能会非常棘手。幸运的是,标准库中的读取器使用的一些方法使其易于流式传输。不过,在使用读取器之前,请查阅其文档
从读取器中流式传输数据
直接从读取器流式传输数据很容易。Read 方法被设计为在循环内调用,每次迭代时,它从源读取一大块数据并将其放入缓冲区 p 中。直到 Read 方法返回io.EOF 错误
以下是一个简单的示例,它使用 string.NewReader(string) 创建的字符串读取器来从字符串源中流式传输字节值:
func main() {
reader := strings.NewReader("Clear is better than clever")
p := make([]byte, 4)
for {
n, err := reader.Read(p)
if err == io.EOF {
break
}
fmt.Println(string(p[:n]))
}
}
上面的源代码用 make([] byte,4) 创建一个 4 字节长的传输缓冲区 p。缓冲区故意保持小于字符串源的长度, 这是为了演示如何从大于缓冲区的源正确传输数据块
更新: Reddit 上有人指出上面的代码中有 bug, 它永远不会捕获非零错误 err != io.EOF . 以下修复了代码:
func main() {
reader := strings.NewReader("Clear is better than clever")
p := make([]byte, 4)
for {
n, err := reader.Read(p)
if err != nil{
if err == io.EOF {
fmt.Println(string(p[:n])) //should handle any remainding bytes.
break
}
fmt.Println(err)
os.Exit(1)
}
fmt.Println(string(p[:n]))
}
}
自定义一个 io.Reader
上一节使用标准库中的现有 IO 读取器实现。现在,让我们看看如何编写自己的读取器。以下是 io.Reader 的简单实现,它从流中过滤掉非字母字符。
package main
import (
"fmt"
"io"
)
// alphaReader is a simple implementation of an io.Reader
// that streams only alpha chars from its string source.
type alphaReader struct {
src string
cur int
}
func newAlphaReader(src string) *alphaReader {
return &a