Go io.Reader 与 io.Writer
Go 中最核心的两个 I/O 接口,通过统一的 Read/Write 契约将文件、网络、内存等所有数据源抽象为可组合的流。
#type / concept
#status / growing
#tech / dev
#resource / go
[!info] related notes
- 所属 MOC: Go 类型系统与抽象
- 前置概念: Go 接口
- 并列概念: Go 泛型
Go io.Reader 与 io.Writer
一句话定义
io.Reader 和 io.Writer 是 Go I/O 体系的基石接口,任何实现了 Read([]byte) (int, error) 或 Write([]byte) (int, error) 的类型都能参与统一的数据流操作。
核心机制 / 工作原理
io.Reader 接口:
type Reader interface {
Read(p []byte) (n int, err error)
}
契约:将数据读入 p,返回实际读取的字节数 n。当数据读完时返回 io.EOF。n 可以小于 len(p),这是合法的(短读取)。
io.Writer 接口:
type Writer interface {
Write(p []byte) (n int, err error)
}
契约:将 p 中的数据写出,返回实际写入的字节数 n。n < len(p) 时必须返回非 nil error。
实现这两个接口的常见类型:
| 类型 | Reader | Writer |
|---|---|---|
*os.File | Yes | Yes |
*net.Conn | Yes | Yes |
*bytes.Buffer | Yes | Yes |
*strings.Reader | Yes | No |
*http.Response.Body | Yes | No |
可组合的装饰器(Decorator Pattern):
bufio.NewReader/Writer— 加缓冲层,减少系统调用io.TeeReader(r, w)— 读取的同时将数据写入另一处(如计算哈希)io.MultiReader(r1, r2, ...)— 串联多个 Readerio.LimitReader(r, n)— 最多读取 n 字节io.MultiWriter(w1, w2, ...)— 同时写入多个 Writer
标准工具函数:
// 将 src 的全部内容复制到 dst
io.Copy(dst Writer, src Reader) (int64, error)
// 读取 r 的全部内容
io.ReadAll(r Reader) ([]byte, error)
最小例子 / 最小场景
// 从 HTTP 响应体读取数据并写入文件,同时计算 SHA256
func downloadAndHash(url, filepath string) (string, error) {
resp, err := http.Get(url)
if err != nil {
return "", err
}
defer resp.Body.Body.Close()
f, err := os.Create(filepath)
if err != nil {
return "", err
}
defer f.Close()
h := sha256.New()
// TeeReader: 读取的同时将数据写入 h
reader := io.TeeReader(resp.Body, h)
// MultiWriter: 同时写入文件
_, err = io.Copy(f, reader)
if err != nil {
return "", err
}
return hex.EncodeToString(h.Sum(nil)), nil
}
这里 io.TeeReader + io.Copy 的组合实现了”读一遍、写文件、算哈希”三个操作,而不需要自己管理缓冲区和循环。
为什么重要
- 统一抽象:函数签名只依赖
io.Reader/io.Writer,不关心底层是文件、网络还是内存,极大提升了代码复用性。 - 可组合性:装饰器可以像积木一样叠加(缓冲 → 限制大小 → 计算哈希),每个装饰器只做一件事。
- 可测试性:测试时用
bytes.Buffer或strings.Reader替代真实 I/O,无需文件系统或网络。 - 零拷贝理念:数据从 Reader 流向 Writer,不需要先全部读入内存再写出,适合处理大文件。
边界与易混淆点
io.EOF不是错误,而是信号——表示数据已读完。只有当n == 0且err == io.EOF时才应终止循环;短读取时可能n > 0且err == io.EOF。Read返回n < len(p)是完全合法的,不要假设一次Read能填满整个切片。io.Copy内部会处理短读取和短写入,通常优先使用它而非手动循环。*http.Response.Body必须关闭(defer resp.Body.Close()),否则底层 TCP 连接不会被复用。bytes.Buffer既是 Reader 又是 Writer,但它不是并发安全的。