必须用 os.Open + io.CopyN 流式分块,因 os.ReadFile 会全量加载致 OOM,手动 Read 易丢末尾数据,bufio.Scanner 不支持精确分块,strings.NewReader 仍驻留内存;io.CopyN 可控大小、自动处理末块、无需预估总长。

必须用 os.Open + io.CopyN 流式分块,不能用 os.ReadFile;否则几 GB 文件直接触发 OOM。
为什么不能用 os.ReadFile 或手动 Read 循环
常见错误现象:程序运行时内存暴涨、卡死、被系统 OOM killer 杀掉。
-
os.ReadFile会把整个文件一次性加载进内存,1GB 文件就占 1GB 堆空间,GC 压力陡增 - 手动
Read循环容易忽略返回值n,把n 误判为错误而提前退出,导致末尾数据静默丢失 -
bufio.Scanner不支持字节级切分,超长行会 panic,且无法控制每块精确大小 - 用
strings.NewReader包裹大字符串再分片,本质仍是全量驻留内存,没解决根本问题
io.CopyN 是唯一稳的分块写入方式
它能精确控制每块字节数,自动处理最后一块不足的情况,且不依赖文件总大小预估。
- 每次调用
io.CopyN(dst, src, chunkSize),最多复制chunkSize字节,返回实际写入数n - 当
n 或err == io.EOF,说明已到文件尾,应立即停止 - 源必须是
*os.File(由os.Open返回),不能是bufio.Reader—— 后者不支持io.ReaderAt,无法保证偏移对齐 - 目标分片文件名建议零填充:
fmt.Sprintf("chunk_%05d.bin", i),避免ls排序错乱
写入目标文件时容易踩的坑
分块后若需合并或校验,顺序、落盘、一致性三者缺一不可。
- 别用
os.WriteFile写分片——它会覆盖同名文件,高并发下极易内容错位;统一用os.Create+io.CopyN+dst.Close() - 写完每块后调
dst.Sync(),尤其在 NFS、云盘挂载点上,否则断电或 crash 可能丢失最后一块 - 合并时不能只靠
filepath.Glob("chunk_*"),必须提取序号转int后排序,否则chunk_1、chunk_10、chunk_2会错序 - 每个分片写入后记录
md5和size到manifest.json,别只信文件名推算 offset —— 文件系统重命名或 mv 操作会破坏假设
缓冲与并发不是越多越好
写入本质是 I/O 密集型操作,盲目并发反而拉低吞吐,还可能引发竞争或错乱。
- 写入同一目标文件?必须串行:用
chan []byte+ 单个bufio.Writer消费者,禁止多 goroutine 直接 Write - 写入多个独立分片?可并发,但 goroutine 数硬限为 4–16(SSD)或 ≤4(HDD),用带缓冲 channel 当信号量:
sem := make(chan struct{}, 8) - 用
bufio.NewWriterSize(file, 256*1024)包装 writer,减少系统调用;但记得在Close()前显式w.Flush(),defer 在 panic 时无效 - 最后一块写入后,
dst.Close()会隐式 flush,但不 guarantee 落盘 —— 关键场景仍需dst.Sync()
真正难的不是“怎么切”,而是“切完怎么不丢、不错、不慢”。文件名零填充、每块显式 Sync、合并前校验 manifest,这三步漏掉任意一个,几个月后都可能暴露静默损坏。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











