应使用os.open和io.copyn流式分块:每次调用io.copyn(dst, src, chunksize)精确复制最多chunksize字节,遇eof时若n>0仍需保存最后一块,仅当n==0才终止;避免os.readfile全量加载致oom。

用 os.File 和 io.CopyN 控制写入字节数
Go 里没有内置“按大小切分文件”的函数,得靠手动控制每次写入的字节数。核心思路是:打开源文件,用 io.CopyN 每次只拷贝指定字节数到新文件,重复直到读完。注意别直接用 io.Copy,它会一股脑全写进去,失去切分能力。
常见错误是把 chunkSize 当成缓冲区长度传给 io.Copy —— 它根本不接受这个参数。正确做法是循环中调用 io.CopyN(dst, src, chunkSize),它会精确复制最多 chunkSize 字节,并返回实际复制数和错误。
-
io.CopyN遇到 EOF 时返回io.EOF,但只要复制了部分字节,仍返回n > 0,需检查n == 0才算真正结束 - 源文件读取位置由
*os.File的内部 offset 自动维护,无需手动Seek - 目标文件每次新建(
os.Create),避免残留或覆盖
处理最后一块不足 chunkSize 的情况
最后一块文件往往小于设定大小,比如源文件 1025 字节、切分大小为 1024 字节,第二块只有 1 字节。这时候 io.CopyN 会返回 n = 1 和 io.EOF,属于正常行为,不能当作错误丢弃。
关键判断逻辑是:只要 n > 0,就说明有数据写入成功,应保存该分片;只有 n == 0 且 err != nil 才代表异常中断。
- 不要因为
err == io.EOF就提前退出循环,否则最后一块丢失 - 建议在每次
io.CopyN后立即检查n:若n == 0,break;否则继续下一轮 - 文件名可按序号命名,如
part_001.bin、part_002.bin,方便后续合并
避免内存爆掉:不用 []byte 全读再切
有人想先 os.ReadFile 把整个文件加载进内存,再按偏移切片——这在处理 GB 级文件时直接 OOM。Go 的优势恰恰在于流式处理,全程只需固定大小的 buffer(甚至不显式声明 buffer,io.CopyN 内部会用默认 32KB 缓冲)。
实操上完全不需要 make([]byte, chunkSize) 或类似操作。用 io.CopyN + *os.File 组合,天然支持大文件,内存占用稳定在几十 KB 级别。
- 显式分配大 buffer 不仅多余,还可能因对齐、GC 压力导致性能下降
- 如果真需要自定义 buffer(例如特殊加密场景),用
bufio.Writer包裹目标文件,但普通切分没必要 - 注意关闭每个打开的
*os.File,尤其是目标文件,否则可能触发 “too many open files” 错误
Windows 下路径和权限的兼容性细节
在 Windows 上用相对路径如 "parts/part_001.bin" 时,如果 parts/ 目录不存在,os.Create 会失败并报 open parts/part_001.bin: The system cannot find the path specified。Linux/macOS 同样报错,但很多人只在 Windows 上遇到,误以为是平台问题。
解决方法很简单:在循环前用 os.MkdirAll("parts", 0755) 确保父目录存在。权限位 0755 在 Windows 上会被忽略,但写全更稳妥。
- 文件名中避免使用
:、*、?等 Windows 非法字符,纯数字序号最安全 - 如果源文件正在被其他进程写入,
os.Open可能成功,但后续io.CopyN会读到不一致内容,应用层需自行加锁或约定只切分静态文件 - 跨平台代码建议用
filepath.Join("parts", filename)拼路径,而不是硬写/或\
io.CopyN 的返回值组合必须同时判断 n 和 err,且 io.EOF 是合法终止信号,不是错误**。漏掉这个,要么丢最后一块,要么无限循环。golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











