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

Go 怎么按指定顺序合并多个文本文件

来源:17golang原创

时间:2026-09-06 01:54:28 120浏览 收藏

Go 按指定顺序合并多个文本文件,推荐把路径列表当成唯一顺序来源:先用 os.Open 逐个打开,再用 io.MultiReader 把这些文件串成一个读取器,最后通过 io.Copy 流式写入输出文件。这样不用把所有文本一次性读进内存,也不会因为目录遍历顺序不确定而拼错内容。

核心写法是 io.MultiReader(readers...)。它只负责按传入顺序连续读取,不会自动补换行;输入文件、输出文件和中途写入失败,都要在外层代码里明确处理。

要点速览:
  • 用切片顺序决定合并顺序,不要把文件名排序规则藏在目录遍历里。
  • io.MultiReader 是流式串联读取器,配合 io.Copy 适合大文件。
  • 每个输入文件都要关闭;出错时可删除可能只写了一半的输出文件。

先把文件顺序和换行规则定下来

假设有 part-01.txtpart-02.txtpart-03.txt,业务要求它们依次出现在 merged.txt 中,就直接把这个顺序写进切片。不要依赖 os.ReadDir 返回的顺序,也不要用 map 保存待合并路径。

对象职责需要确认的边界
paths规定输入先后不能包含输出文件本身
os.File提供每个输入的 Reader打开成功后必须关闭
io.MultiReader连续暴露多个 Reader不会自动添加换行
merged.txt接收合并结果写入失败时可能是不完整文件

文件末尾是否已有换行会直接影响合并后的边界。如果第一段没有换行,下一段的首行会紧接在上一段末尾。需要严格按“文件内容原样拼接”时不补内容;需要每段独占一行时,应把分隔符作为明确的额外 Reader 或改用逐文件复制方案。

用 io.MultiReader 流式合并文本文件

package main

import (
    "fmt"
    "io"
    "os"
)

// mergeFiles 按 paths 的顺序合并文件,并返回实际写入的字节数。
func mergeFiles(paths []string, output string) (written int64, err error) {
    out, err := os.Create(output)
    if err != nil {
        return 0, fmt.Errorf("创建输出文件失败: %w", err)
    }
    defer func() {
        // 输出文件的关闭错误不能覆盖前面更具体的读写错误。
        if closeErr := out.Close(); err == nil && closeErr != nil {
            err = fmt.Errorf("关闭输出文件失败: %w", closeErr)
        }
    }()

    files := make([]*os.File, 0, len(paths))
    readers := make([]io.Reader, 0, len(paths))
    for _, path := range paths {
        file, openErr := os.Open(path)
        if openErr != nil {
            // 已打开的输入文件在失败返回前也要关闭。
            for _, opened := range files {
                _ = opened.Close()
            }
            return 0, fmt.Errorf("打开 %q 失败: %w", path, openErr)
        }
        files = append(files, file)
        readers = append(readers, file)
    }
    defer func() {
        // MultiReader 读完不会替调用方关闭底层文件。
        for _, file := range files {
            _ = file.Close()
        }
    }()

    reader := io.MultiReader(readers...)
    written, err = io.Copy(out, reader)
    if err != nil {
        return written, fmt.Errorf("合并文件失败: %w", err)
    }
    return written, nil
}

func main() {
    paths := []string{"part-01.txt", "part-02.txt", "part-03.txt"}
    written, err := mergeFiles(paths, "merged.txt")
    if err != nil {
        fmt.Println(err)
        return
    }
    fmt.Printf("合并完成,写入 %d 字节\n", written)
}

这里的关键点是:readers 的排列决定读取顺序,io.Copy 只需要面对一个 Reader;当当前文件读到 EOF 后,MultiReader 才会继续下一个文件。官方文档说明,输入 Reader 返回非 EOF 错误时,该错误会从串联读取器继续返回。

Go io.MultiReader 将有序 os.File 读取器串联并交给 io.Copy 写入输出文件的静态结构图
图1:看清 paths、os.File、io.MultiReader、io.Copy 与输出文件之间的静态关系,合并顺序由 readers 切片决定。

什么时候要补分隔符,什么时候保持原样

io.MultiReader 只做逻辑上的连续读取,不知道“文本段落”或“每个文件一行”的业务含义。若每个输入文件都保证以换行结尾,直接合并就可以得到自然的行边界;如果文件可能没有结尾换行,就要在方案里补上分隔符。

一种简单做法是把每个文件和一个换行字符串组成小的 Reader 组,再将所有组展平后传给 MultiReader。如果分隔符必须是固定编码,使用 strings.NewReader("\n");如果要写入标题、文件名或自定义标记,则应把这些内容当成明确的数据源,而不是指望 API 自动生成。

// 每个文件后追加一个换行,避免相邻文件的末行粘连。
func readersWithSeparator(files []*os.File) []io.Reader {
    readers := make([]io.Reader, 0, len(files)*2)
    for _, file := range files {
        readers = append(readers, file, strings.NewReader("\n"))
    }
    return readers
}

上面的片段需要额外导入 strings,并且只适合“每段后都补一个换行”的规则。若最后一段不应多出空行,最好在写入循环中单独处理分隔符,先写第二段之前的分隔符,再复制当前文件。

出错时检查句柄和不完整输出

合并过程中最容易漏掉的不是 API,而是资源边界。打开第 3 个输入文件失败时,第 1、2 个文件已经成功打开,必须在返回前关闭;输出写入失败时,merged.txt 可能已经包含前几段内容,不能把它当成完整结果继续下游处理。

如果输出文件只允许“完整成功”状态,可以在 io.Copy 失败后删除它,或者先写入同目录临时文件,全部成功后再由业务代码完成替换。后一种方案还能避免读取方看到半成品,但替换策略要结合操作系统、文件权限和并发读者单独设计。

Go 文件合并中输入文件句柄、读取器边界、输出文件和错误处理边界的静态关系图
图2:输入句柄由调用方负责关闭,输出文件可能处于完整或部分写入状态,错误处理应覆盖这两个资源边界。

合并结果的检查清单

  • 输入路径是否已经按业务顺序固定,且没有把输出路径混入输入列表?
  • 每个 os.Open 成功后是否都有对应的 Close
  • 相邻文件的末尾和开头是否需要换行或自定义分隔符?
  • io.Copy 返回错误时,调用方是否会阻止读取不完整输出?
  • 大文件场景是否避免了 os.ReadFile 加大切片带来的额外内存占用?

常见问题

io.MultiReader 会自动按照文件名排序吗?

不会。它按照传入 Reader 的顺序读取,所以排序、筛选和去重都应在生成 paths 之前完成。

合并文本一定要使用 bufio.Scanner 吗?

不需要。只做字节级拼接时,io.Copy 更直接,也不会受到 Scanner 默认 token 长度和逐行重建的影响。

输入文件很多时能不能并发读取后再合并?

如果结果必须保持顺序,并发读取通常会增加缓存和协调成本。先用顺序的 MultiReader;只有业务明确允许乱序或已有分片调度时,再单独设计并发合并协议。

把顺序、资源和结果状态一起管好

Go 合并多个文本文件的最小可靠方案是:用有序路径切片创建输入文件,用 io.MultiReader 做连续读取,用 io.Copy 写入输出,再把换行、关闭和失败清理写成明确规则。这样既能支持大文件流式处理,也能让“顺序错了”“段落粘连”和“生成半个文件”这些问题在代码层面有对应的检查点。

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