应使用 bufio.scanner 分块读取文件,避免 os.readfile 一次性加载大文件导致 oom;处理日志、csv、jsonl 等行式数据时,需边读边处理,scanner 默认单行上限 64kb,超限时可调用 scan.bytes() 或通过 scanner.buffer 扩容。

用 bufio.Scanner 分块读文件,别用 os.ReadFile
一次性读大文件进内存,Go 程序直接 OOM。尤其处理日志、CSV、JSONL 这类行式数据时,os.ReadFile 是最常见踩坑点。
实际做法是让程序“边读边处理”,每次只拿一行或一小块:
-
bufio.Scanner默认每行最多 64KB,适合日志、文本类;若单行超限,会报scanner: token too long,需提前调Scan.Bytes()或设更大缓冲:scanner.Buffer(make([]byte, 64*1024), 1 - 对二进制流或自定义分隔符(比如 JSONL 中的换行),改用
bufio.Reader.ReadLine()或ReadBytes('\n')更可控 - 别在
for scanner.Scan()循环里做阻塞操作(如同步 HTTP 请求、未加超时的 DB 写入),否则整个流水线卡住
用 channel + goroutine 拆解“读-处理-写”三阶段
纯顺序处理再快也单核,但并发不是越多越好——盲目起几百个 goroutine 写数据库,反而压垮连接池或触发锁争用。
推荐固定 worker 数量的管道模型:
- 一个 goroutine 负责读(生产者),把解析后的结构体发到
inCh chan *Record - N 个 goroutine 从
inCh消费,处理并写入outCh chan *Result - 一个 goroutine 从
outCh收结果,批量落库或写文件 - channel 缓冲区大小设为 100~1000,太小易阻塞,太大吃内存;用
close(inCh)通知消费者结束
处理 JSONL / CSV 流时,避免反复 json.Unmarshal 分配内存
每行一个 JSON 对象(JSONL)或 CSV 行,如果每次都 json.Unmarshal(data, &v),GC 压力巨大。实测百万行可多占 30%+ 堆内存。
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
更省的方式:
- 复用结构体指针:
var record MyStruct; json.Unmarshal(data, &record),但注意字段值是否被意外保留 - 对简单结构,用
json.RawMessage延迟解析:“先提 key,真要用再解”,减少无用反序列化 - CSV 场景优先用
encoding/csv.Reader的Read()方法,它内部复用[]string切片,比手动strings.Split省内存
流式写入时慎用 os.Stdout 或未缓冲的 *os.File
每处理一条就 fmt.Fprintln(os.Stdout, ...),等于每条都触发一次系统调用,吞吐暴跌。本地测试可能看不出,上生产一压就暴露。
正确姿势:
- 输出到文件时,包一层
bufio.NewWriter(f),并在最后w.Flush() - 若要实时看日志,用
log.SetOutput(bufio.NewWriter(os.Stderr))并定期log.Writer().(*bufio.Writer).Flush() - 写数据库批量提交:攒够 100 条或 100ms 触发一次
tx.Exec(...),别单条 commit
真正难的不是写流式代码,而是判断哪一环成了瓶颈——有时是磁盘 I/O,有时是 GC 频率,有时是下游服务响应慢拖垮 channel。上线前务必用 pprof 抓 CPU 和 heap profile,别靠猜。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










