用 io.Copy 合并文件流最轻量,目标文件需按需求选打开模式:覆盖用 O_CREATE|O_WRONLY|O_TRUNC,追加用 O_CREATE|O_WRONLY|O_APPEND;分片上传后须提取数字排序,避免字符串排序错乱。

用 io.Copy 合并文件流,但必须注意打开模式
直接 io.Copy(dst, src) 是最轻量、内存占用最低的合并方式,适合 GB 级文件。但出错最多的地方是目标文件打开方式写错:
常见错误是用 os.O_CREATE | os.O_WRONLY 打开目标文件——这会清空已有内容,且无法追加;
想按顺序拼接多个源文件,必须用 os.O_CREATE | os.O_WRONLY | os.O_TRUNC(覆盖写入);
想在已有文件末尾追加,必须用 os.O_CREATE | os.O_WRONLY | os.O_APPEND(且目标文件需已存在,否则 O_APPEND 在首次创建时行为等价于 O_WRONLY)。
Windows 下 O_APPEND 会忽略 Seek,所以别手动 WriteAt,一律走 io.Copy。
切片还原时文件名排序不等于自然序,必须显式 sort.Slice
分片上传后,服务端收到的文件名常为 part_1、part_10、part_2,按字符串排序结果是 1、10、2,直接 filepath.Glob("part_*") 后合并会导致数据错乱。
正确做法是提取数字部分再排序:
- 用正则或
strings.TrimPrefix+strconv.Atoi解析索引 - 用
sort.Slice(files, func(i, j int) bool { return idx[i] - 避免依赖文件系统返回顺序,
os.ReadDir不保证顺序,filepath.Walk更不可靠
合并大文件时别用 ioutil.ReadFile,流式读写才安全
ioutil.ReadFile(或 os.ReadFile)会把整个文件加载进内存,1GB 文件就占 1GB 堆,GC 压力陡增,还可能触发 OOM。
生产环境必须用流式:
- 每个分片用
os.Open打开,传给io.Copy - 目标文件用
os.OpenFile配合O_WRONLY | O_CREATE | O_TRUNC - 若需控制缓冲区大小(如降低磁盘 I/O 峰值),可包一层
bufio.NewReaderSize(src, 1 - 合并完成后调用
dst.Sync()确保数据落盘,防止断电丢片
分片校验和合并触发时机不能只信 total_chunks
客户端传来的 total_chunks 参数不可信,可能被篡改或未发完就中断。
真正可靠的合并触发条件是:
- 用
sync.Map或 Redis 记录每个file_id已接收的chunk_index集合 - 收到新分片时,先校验
chunk_hash(如 SHA256 前 8 字节),失败则拒收 - 仅当集合长度 == 实际接收数,且最大索引 + 1 == 预期总数(或用原子计数器比对)时,才启动合并
- 合并前再次遍历所有分片文件,用
os.Stat确认存在且非空,跳过损坏或零字节文件
立即学习“go语言免费学习笔记(深入)”;


















