应使用 bufio.scanner 流式读取大文件:每行单独处理,内存恒定(默认64kb缓冲区),自动按换行符切分;需 defer file.close() 防句柄泄漏;超长行需提前调 scanner.buffer 设置最大容量。

大文件逐行查找别用 ioutil.ReadFile
它会把整个文件塞进内存,1GB 文件直接 OOM;哪怕 100MB,在低配容器里也容易触发 GC 频繁或被系统 kill。真实场景中,日志、导出数据、CSV 等动辄几百 MB 是常态。
正确做法是用 bufio.Scanner 流式读取:
- 每行单独处理,内存占用恒定(默认缓冲区 64KB,可调)
- 自动按
\n、\r\n切分,兼容多数文本格式 - 必须在循环外加
defer file.Close(),否则文件句柄泄漏 - 遇到超长行(如单行 JSON)会报
token too long,此时要提前调scanner.Buffer(make([]byte, 64*1024), 1 扩容
想搜“最后一行”或“最后 N 行”?别从头扫
用 os.File.Seek 反向读更高效——尤其对持续追加的日志文件。原理是先 file.Stat() 拿到文件大小,再从末尾倒着逐字节读,数够换行符就停。
关键点:
-
Seek(0, io.SeekEnd)定位到末尾,再用负偏移往回跳 - 要同时识别
\n和\r\n,Windows 和 Unix 换行不统一 - 读到文件开头时停止,避免
Seek(-1, io.SeekCurrent)越界 panic - 实际代码里建议封装成函数,比如
tailFile(file *os.File, n int) ([]string, error)
需要反复查同一文件?建轻量索引比每次重扫强
如果一个 500MB 日志每天被查几十次关键词,每次都 Scanner 过一遍纯属浪费 CPU。这时可在首次扫描时顺便建个行号索引:
- 遍历一次,记录每行起始偏移(
file.Seek(0, io.SeekCurrent)在每行 Scan 前调用) - 存成
map[int64]int(偏移 → 行号)或排序切片,写入本地.idx文件 - 后续搜索先二分查偏移,再
Seek+ReadString('\n')精准读行 - 注意:文件被截断或追加后,索引需标记失效或增量更新,否则读错位置
并发搜多个大文件?小心 too many open files
用 filepath.WalkDir 遍历目录时开 goroutine 处理每个文件,很容易打爆系统文件描述符上限(Linux 默认常为 1024)。
必须限流:
- 用带缓冲的 channel 控制并发数,例如
sem := make(chan struct{}, 10) - 每个 goroutine 开始前
sem ,结束后 <code> - 别用
sync.WaitGroup单纯等结束——没限流的 WaitGroup 只会让崩溃来得更猛 - 打开文件失败(如权限不足)要跳过,不能
panic或阻塞整个流程
真正难的不是读文件,而是判断“这行值不值得索引”——比如日志里带毫秒时间戳的行,你索引的是原始字符串还是解析后的 time.Time?字段是否要归一化?这些决策一旦写死,后期改起来比重建索引还疼。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











