Go 怎么把多个 Reader 拼成一个连续输入流
来源:17golang原创
时间:2026-09-07 01:01:58 426浏览 收藏
如果几个输入源本来就有先后顺序,Go 不需要先把它们全部读进内存再拼接。直接用 io.MultiReader(reader1, reader2, reader3) 得到一个新的 io.Reader,消费端会先读完 reader1,再读 reader2,最后读 reader3。所有输入正常返回 EOF 后,组合 Reader 才返回 EOF;中间出现非 EOF 错误,则把错误交给调用方处理。
io.MultiReader只做逻辑拼接,读取仍然是流式的,不会主动汇总所有字节。- 传入顺序就是读取顺序;一个 Reader 正常结束后才会尝试下一个。
- 它不关闭底层
io.ReadCloser,也不提供回退、随机读取或并发安全保证。
用 io.MultiReader 建立顺序输入
典型场景是把文件头、分片文件或多段字符串交给同一个解析器。下面的示例把三个字符串 Reader 组成一个入口,然后由 io.Copy 写到标准输出。这里没有创建一个“拼接后的大字符串”,组合层只保存并切换输入 Reader。
package main
import (
"io"
"log"
"os"
"strings"
)
func main() {
// 参数顺序决定读取顺序:先头部,再正文,最后尾部。
reader1 := strings.NewReader("header\n")
reader2 := strings.NewReader("body\n")
reader3 := strings.NewReader("footer\n")
combined := io.MultiReader(reader1, reader2, reader3)
// 消费端只依赖一个 io.Reader,不需要知道内部有几个输入源。
if _, err := io.Copy(os.Stdout, combined); err != nil {
log.Fatal(err)
}
}

把拼接后的 Reader 交给消费端
组合后的值仍然只是 io.Reader,所以可以接到 io.Copy、bufio.Scanner 或自定义 Read 循环。它适合“只向前读”的接口。若每段输入代表独立的 JSON 文档、CSV 文件或协议帧,拼接之前要确认边界不会被吞掉;必要时把换行符、长度字段或分隔符作为单独 Reader 放入序列。
可以用下面的表快速判断组合层的职责:
| 对象 | 负责什么 | 不负责什么 |
|---|---|---|
io.MultiReader | 按顺序转发 Read 调用、跳过正常结束的输入 | 解析格式、缓存全部内容、关闭底层资源 |
| 消费端 | 处理字节、判断返回错误、决定是否继续 | 假设每次 Read 都填满缓冲区 |
| 调用方 | 管理打开的文件、连接和关闭时机 | 把 EOF 当作业务失败 |
区分 EOF 与真实错误
EOF 是一个输入源正常读完的信号,不代表组合失败。MultiReader 会继续尝试后面的 Reader,直到全部输入都结束。相反,某个 Reader 返回非 EOF 错误时,组合 Reader 会返回这个错误,后面的输入不会被当成已成功消费。消费循环仍要先处理 n > 0 的字节,再判断 err:
func consume(combined io.Reader) error {
buf := make([]byte, 32)
for {
// Read 可能同时返回字节和错误,先处理有效字节。
n, err := combined.Read(buf)
if n > 0 {
handle(buf[:n])
}
if err == io.EOF {
// 所有输入正常结束,退出读取循环。
return nil
}
if err != nil {
// 非 EOF 错误需要保留上下文,不能当成正常结束。
return fmt.Errorf("读取组合输入失败: %w", err)
}
}
}
实际文件中还要导入 fmt,并把示例里的 handle 换成业务处理函数。关键点是不要因为 err 非空就丢掉同一次调用返回的有效字节;Reader 接口允许二者同时出现。

关闭责任和方案边界
io.MultiReader 的输入类型是 io.Reader,因此它不会自动调用底层 Reader 的 Close。如果输入来自 os.File、HTTP 响应体或数据库导出的流,仍由打开它们的代码负责 Close。多个资源一起使用时,通常在创建组合 Reader 的函数里集中登记关闭动作,避免把生命周期误交给只负责转发读取的对象。
它也不是所有“合并输入”问题的答案:需要随机定位时应保留各自的 io.Seeker;需要并发读取时要先确认底层实现的并发约束;需要把同一份输入复制给多个输出时,应考虑 io.TeeReader 或 io.MultiWriter。这几个 API 名字相似,但方向不同,不能互换。
相关问题
io.MultiReader 会把多个文件一次性读入内存吗?
不会。它按需调用当前 Reader 的 Read,适合顺序处理大文件;真正占用内存的大小取决于消费端的缓冲和解析策略。
某个 Reader 返回 EOF 后还能回到它继续读吗?
不能依赖这种行为。MultiReader 会把正常结束的 Reader 视为已完成并转向下一个,若业务需要回退,应自行保留可 Seek 的源。
MultiReader 能自动关闭文件吗?
不能。关闭文件、响应体或连接仍是调用方责任,建议在打开资源的同一层安排 defer Close 或明确的集中清理逻辑。
-
369 收藏
-
344 收藏
-
464 收藏
-
327 收藏
-
349 收藏
-
240 收藏
-
479 收藏
-
408 收藏
-
271 收藏
-
136 收藏
-
249 收藏
-
127 收藏
-
140 收藏
-
157 收藏
-
160 收藏
-
193 收藏
-
325 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 立即学习 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 立即学习 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 立即学习 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 立即学习 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 立即学习 485次学习