os.ReadFile必须禁用,因其内部调用io.ReadAll强制预分配等于文件大小的[]byte,1.2GB文件导致runtime: out of memory panic;正确方案是用io.Reader链式流式处理,如io.Copy或io.CopyBuffer配合os.Open,内存恒定可控。

别用 os.ReadFile,它会在读取 GB 级文件时直接触发 runtime: out of memory panic;真正能上线的方案,是绕过“一次性加载”模型,用 io.Reader 链式驱动,让数据流经内存而非堆积在内存。
为什么 os.ReadFile 必须禁用
它内部调用 io.ReadAll,强制预分配一块等于文件大小的 []byte。1.2GB 文件 → 1.2GB 堆内存瞬间分配,GC 来不及反应,系统直接 kill 进程。错误现象不是报错,而是进程无声退出、dmesg 显示 Out of memory: Kill process。哪怕你只 grep 一行,Go 也先 malloc 完再找——这不是性能问题,是模型错误。
io.Copy 是最简可靠的流式入口
它不缓存、不解析、不假设格式,只做一件事:把 src 的数据搬进 dst,内存恒定在 32KB(默认缓冲区)。适合加密、压缩、复制、上传等无损二进制流转场景。
- 打开源文件用
os.Open,目标文件用os.O_CREATE | os.O_WRONLY | os.O_TRUNC - 必须显式
defer src.Close()和defer dst.Close(),否则 fd 泄漏 -
io.Copy返回的error可能是写入中途发生的(如write: broken pipe),不是 EOF,要单独判断 - 若目标是 NFS、USB 或 HTTP 响应体,吞吐会掉,此时换
io.CopyBuffer(dst, src, make([]byte, 1024*1024)),buf 推荐 64KB–1MB
文本类大文件优先用 bufio.Scanner,但得绕开坑
日志过滤、CSV 行处理、JSON Lines 清洗等场景,bufio.Scanner 比 bufio.Reader 更安全简洁,但它默认单行上限 64KB,遇到含 base64 的日志或嵌套 JSON 就 panic。
立即学习“go语言免费学习笔记(深入)”;
- 超长行必须提前调
scanner.Buffer(make([]byte, 64*1024), 10*1024*1024),第二个参数是硬上限,超了仍 panic -
scanner.Text()返回的是底层缓冲区子切片,若存进全局 slice 或 map,整个缓冲区无法 GC;要保存内容,改用strings.Clone(scanner.Text())或string(scanner.Bytes()) - 空行返回
[]string{},不是 error,别用if err != nil判断 - 每轮循环结束后必须检查
scanner.Err():EOF是正常结束,但syscall.EPIPE或磁盘 I/O 错误必须中断 pipeline
Gin 框架上传大文件不能碰 ctx.FormFile
ctx.FormFile 会把整个 multipart 文件载入内存再返回 *multipart.FileHeader,几百 MB 就 OOM。真实流式上传必须跳过 Gin 自动解析,直取底层 reader。
- 禁用自动解析:不要调
r.MaxMultipartMemory(),也不主动调r.ParseMultipartForm(),否则Request.Body已被读空 - 用
c.Request.MultipartReader()获取标准multipart.Reader,再循环reader.NextPart() - 拿到
part后,它本身就是io.Reader,可直接io.Copy到磁盘或对象存储 - 配合
http.MaxBytesReader(w, r.Body, 500*1024*1024)限流防攻击,校验 SHA256 用io.TeeReader(limitReader, hash)边读边算
真正难的不是“怎么读”,而是块边界与业务逻辑的对齐——比如断点续传要记 offset,加密流要对齐 cipher block size,这些地方缓冲区设置和 reader 封装方式会直接影响正确性,而不是性能。


















