应显式配置 bufio.scanner 缓冲区上限并检查错误:scanner.buffer(make([]byte, 641024), 10241024),每次 scanner.scan() 后调用 scanner.err();scanner.text() 返回值需深拷贝才能长期持有;对行边界不可靠场景,优先使用 bufio.reader.readstring('\n')。

别用 os.ReadFile,也别无脑信 bufio.Scanner 默认配置——它会在单行超 64KB 时 panic,且不检查错误就以为读完了,实际是丢数据或崩溃。
scanner.Buffer() 必须显式调大,但别设成 math.MaxInt32
默认的 scanner.Buffer 第二个参数(最大 token 长度)是 64KB,遇到带 base64、嵌套 JSON 或 minified HTML 的日志行,直接触发 scanner.ErrTooLong。这不是异常,是设计行为。
- 安全做法:根据业务中“最长可能行”设上限,比如支持 1MB 行:
scanner.Buffer(make([]byte, 64*1024), 1024*1024) - 危险操作:写
scanner.Buffer(nil, math.MaxInt32)—— 一旦文件里有缺失换行的巨型块,Scanner会尝试分配 GB 级内存,OOM 瞬间发生 - 必须检查错误:每次
scanner.Scan()后,要立刻调scanner.Err(),不能只靠!scanner.Scan()判断结束
scanner.Text() 返回的字符串不能长期持有
scanner.Text() 返回的是内部缓冲区的 string 视图,下一次 Scan() 就会覆盖底层字节数组。如果你把它塞进 []string 或 map[string]struct{},最后所有元素都变成最后一行的内容。
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
- 流式处理(过滤、计数、解析)可直接用
scanner.Text(),无需拷贝 - 需要保留多行内容时,必须深拷贝:
line := strings.TrimSpace(scanner.Text())(strings.TrimSpace内部已做拷贝)或line := append([]byte(nil), scanner.Bytes()...) - 避免用
scanner.Bytes()直接存 map —— 它返回的是缓冲区切片,整块缓冲区都被强引用,GC 不会回收
bufio.Reader.ReadString('\n') 更可控,尤其当行边界不可靠时
当你要处理 Windows 行尾、空行分隔、或末行无换行符的文件时,bufio.Reader 比 Scanner 更透明,错误路径更明确。
- 初始化时设合理缓冲区:
r := bufio.NewReaderSize(f, 1(1MB),减少系统调用频次 -
ReadString('\n')返回含\n的字符串;若需去除,用strings.TrimSuffix(line, "\n"),再 trim\r - 末行无换行符时,
err == io.EOF但len(line) > 0,必须单独处理 - 不要混用
Reader和底层*os.File.Read—— 缓冲区会让文件偏移错乱,Seek 也不准
真正压内存的不是读取,而是你没断开引用
Go 的 GC 不会立刻归还内存给 OS,更不会主动清空被长期变量隐式引用的缓冲区。很多 OOM 报告里 RSS 持续上涨,根源是某处 map、slice 或闭包悄悄 hold 住了整块 []byte。
- 处理完一批数据后,手动置空局部切片:
buf = buf[:0],帮助编译器识别可复用空间 - 全局缓存或中间状态 map,用完后显式
currentMap = nil,而非只make(map[...]) - 用
runtime.ReadMemStats定期打点,看Alloc和HeapInuse是否阶梯式上涨,而不是只盯top的 RES
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










