登录
推荐 文章 Go 技术 课程 下载 专题 AI
首页 >  Golang >  Go教程

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)
    }
}
Go io.MultiReader 将 reader1、reader2、reader3 按顺序组合后交给 io.Copy 的输入边界与消费边界关系图
图1:io.MultiReader 的静态组成关系:三个 Reader 进入同一个串联入口,再交给 io.Copy 消费。

把拼接后的 Reader 交给消费端

组合后的值仍然只是 io.Reader,所以可以接到 io.Copybufio.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 接口允许二者同时出现。

Go io.MultiReader 中 EOF 正常出口与非EOF错误出口传到消费端的边界关系图
图2:正常 EOF 与非 EOF 错误是两条不同出口;错误发生时,消费端应先处理已读字节再处理 error。

关闭责任和方案边界

io.MultiReader 的输入类型是 io.Reader,因此它不会自动调用底层 Reader 的 Close。如果输入来自 os.File、HTTP 响应体或数据库导出的流,仍由打开它们的代码负责 Close。多个资源一起使用时,通常在创建组合 Reader 的函数里集中登记关闭动作,避免把生命周期误交给只负责转发读取的对象。

它也不是所有“合并输入”问题的答案:需要随机定位时应保留各自的 io.Seeker;需要并发读取时要先确认底层实现的并发约束;需要把同一份输入复制给多个输出时,应考虑 io.TeeReaderio.MultiWriter。这几个 API 名字相似,但方向不同,不能互换。

相关问题

io.MultiReader 会把多个文件一次性读入内存吗?

不会。它按需调用当前 Reader 的 Read,适合顺序处理大文件;真正占用内存的大小取决于消费端的缓冲和解析策略。

某个 Reader 返回 EOF 后还能回到它继续读吗?

不能依赖这种行为。MultiReader 会把正常结束的 Reader 视为已完成并转向下一个,若业务需要回退,应自行保留可 Seek 的源。

MultiReader 能自动关闭文件吗?

不能。关闭文件、响应体或连接仍是调用方责任,建议在打开资源的同一层安排 defer Close 或明确的集中清理逻辑。

声明:本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
相关阅读
更多>
最新阅读
更多>
课程推荐
更多>