io.Reader 是只读、单向、不可重放的接口,需由具体类型实现;读取全部内容优先用 io.ReadAll,手动循环须同时检查 n 和 err;HTTP Body 读完需 Close,多次读取需缓存。

io.Reader 是接口,不是具体类型
Go 里 io.Reader 是一个接口,定义了 Read([]byte) (int, error) 方法。它不负责管理缓冲、不自动重试、也不区分数据来源——文件、网络连接、字符串、bytes.Buffer 都可以实现它。你不能直接 “创建一个 io.Reader”,而是拿到一个已经实现了它的值,比如 os.File、bytes.NewReader() 或 http.Response.Body。
常见错误是试图对 io.Reader 做类型断言或初始化,比如写 var r io.Reader = new(io.Reader) —— 这会编译失败,因为接口不能直接实例化。
读取全部内容用 io.ReadAll,别手动循环
如果目标是把整个流读完(比如小配置文件、API 响应体),优先用 io.ReadAll,而不是自己写 for 循环调 Read。它内部做了扩容和边界检查,比手写更安全。
data, err := io.ReadAll(r)
if err != nil {
log.Fatal(err)
}
// data 是 []byte,可转 string(data)
注意:io.ReadAll 会一次性把所有数据加载进内存,不适合处理 GB 级流;此时应改用 io.Copy 或分块读取。
立即学习“go语言免费学习笔记(深入)”;
- Go 1.16+ 才有
io.ReadAll;旧版本用ioutil.ReadAll(已弃用) - 如果 r 来自
http.Response.Body,记得在读完后Close(),否则连接不会复用
分块读取要检查返回的 n 和 err
手动循环读取时,必须同时检查 n(实际读取字节数)和 err。常见误区是只判断 err != nil 就退出,但 io.EOF 是正常结束信号,而 n == 0 && err == nil 可能是空流或阻塞中(如管道未写入)。
buf := make([]byte, 4096)
for {
n, err := r.Read(buf)
if n > 0 {
// 处理 buf[:n]
}
if err == io.EOF {
break
}
if err != nil {
log.Fatal(err)
}
}
- 每次
Read不保证填满 buffer,n可能小于len(buf) - 不要假设
Read会一次读完一行或一个 JSON 对象;需要按协议解析时,应包装成bufio.Scanner或json.Decoder
别忽略 Reader 的状态不可逆性
io.Reader 是单向、不可重放的。一旦读过,数据就“流走”了。比如从 http.Response.Body 读了一次,再读就是空;bytes.Reader 虽然支持 Seek,但普通 io.Reader 接口本身不提供重置能力。
如果需要多次读取,常见做法是:
- 用
bytes.NewReader(data)把已读内容转成新 Reader - 用
io.TeeReader在读的同时写入bytes.Buffer缓存 - 对 HTTP Body,用
resp.Body = io.NopCloser(bytes.NewReader(cached))替换原 Body
最容易被忽略的是:HTTP 客户端默认不缓存 Body,出错重试时若没提前保存,就再也拿不到原始响应流了。










