io.MultiReader适合按顺序串联多个io.Reader进行单次正向读取的场景。它轻量、零缓冲、零复制,仅在前一个Reader耗尽后自动切换至下一个,要求参数顺序正确且需预先校验非nil,不支持随机访问、倒回、并发读或额外接口透出。

io.MultiReader 适合什么场景
当你需要按顺序读取多个 io.Reader(比如几个文件、几段字节流、或 API 响应体拼接),又不想手动管理切换逻辑时,io.MultiReader 是最轻量的方案。它不缓冲、不复制数据,只是把多个 Reader 串成一个逻辑上的“接力读取器”。不适合需要随机访问、倒回、或并发读取的场景——它只支持单次正向读取。
基本用法和参数顺序很重要
io.MultiReader 接收可变参数 ...io.Reader,按传入顺序依次读取:第一个 Reader 耗尽后自动切到第二个,依此类推。顺序错了,结果就错了。
常见错误是误以为它会合并内容再返回,实际它只是“管道式串联”:
rd := io.MultiReader(
strings.NewReader("hello"),
strings.NewReader(" world"),
strings.NewReader("!"),
)
buf, _ := io.ReadAll(rd) // 得到 []byte("hello world!")
注意:io.MultiReader 不检查传入 Reader 是否为 nil,如果某个参数是 nil,运行时会 panic,务必提前校验。
和 io.MultiWriter / io.TeeReader 容易混淆的点
io.MultiReader 和 io.MultiWriter 名字像,但方向相反:前者是“多读一”,后者是“一写多”;io.TeeReader 是边读边写(类似 Unix 的 tee),不是合并。
性能上,io.MultiReader 几乎零开销——它没有额外分配,每次 Read 都直接转发给当前活跃的 Reader。但要注意:如果中间某个 Reader 的 Read 返回 0, nil(即“空读成功”),MultiReader 会立刻切换到下一个,这可能不符合预期(比如某个 Reader 暂时没数据但还没 EOF)。真正可靠的 EOF 判定仍依赖底层 Reader 自身是否返回 io.EOF。
实际项目中容易漏掉的边界情况
真实使用时,常遇到这些坑:
- 某个 Reader 实际返回了
0, nil(非io.EOF),导致提前跳过后续 Reader —— 比如某些包装过的网络 Reader 在连接空闲时可能这么表现 - Reader 实现了
io.Seeker或io.ReaderAt,但io.MultiReader不透出这些接口,调用会失败 - 想对整个合并流做
io.Copy时,若目标 Writer 失败,前面已读的部分无法回退 —— 它不提供事务语义 - 日志或调试时,很难知道当前读到了哪个 Reader ——
io.MultiReader没有暴露内部状态,也没办法“窥探”当前索引
如果需求变复杂(比如要跳过某段、重试某个 Reader、或需要可观测性),就该考虑自己封装一个带状态的 Reader,而不是硬套 io.MultiReader。

















