应使用 io.CopyN 按固定字节数写入并实时检查返回值,因其能准确反映实际写入量;避免用 Stat() 动态查大小,因存在性能差、状态过期及未刷盘等问题。

用 io.CopyN 每次写固定字节数,写完立刻检查返回值是否小于期望值——等于就继续,小于就说明到上限或 EOF,该切新文件了。
为什么不能靠 os.File.Stat().Size() 动态判断
频繁调用 Stat() 查当前大小不仅慢(每次 syscall),还会在并发写或 NFS 等场景下返回过期值;更关键的是,它无法区分“刚写入但还没 flush”和“真正落盘”,导致切分点漂移甚至重复写入。流式处理必须依赖写操作本身的反馈,而非轮询状态。
io.CopyN 是唯一可靠的字节级截断手段
- 每次调用
io.CopyN(dst, src, chunkSize)返回实际写入字节数n,n 只有两种可能:源已 EOF,或目标写满(比如磁盘满、quota 超限) - 若你目标是“每个分片严格 ≤ 100MB”,那就设
chunkSize = 100 * 1024 * 1024,每次写完检查n —— 成立即切换新文件 - 别用
bufio.Writer包裹目标文件:它的缓冲会掩盖真实写入进度,CopyN返回值不再反映底层设备状态 - 源必须是
*os.File或其他支持io.Reader的流,不能是[]byte或strings.Reader(后者无 EOF 状态可依赖)
写满触发分割时的边界处理要点
实际代码中容易忽略这些细节:
- 最后一次
io.CopyN返回n == 0表示源已空,不是错误,直接 break 即可;返回n > 0 && n 才是正常末尾块,仍要保存为一个合法分片 - 如果目标文件系统有配额(如
ulimit -f),写满时io.CopyN会返回syscall.EFBIG或syscall.ENOSPC,需捕获并显式关闭当前文件、创建新文件重试 - 切换文件前必须调用
currentFile.Close(),否则 Linux 下 fd 泄漏,几百次后too many open files - 新文件名务必用
fmt.Sprintf("part_%03d", i),避免part_1、part_10字典序错乱
最易被跳过的点:不校验最终分片是否真实写满。哪怕你设了 100MB 上限,最后一块也可能是 99.9MB,但若中间某次写入因 I/O 错误只写了 50MB 就中断,程序却没报错继续往下走,后续所有分片都会偏移。所以每次 io.CopyN 后必须判断 n == chunkSize || err == io.EOF,二者都不成立就得 panic 或记录告警。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











