应使用os.Open+bufio.Scanner逐行扫描而非io.ReadAll,因后者易OOM、二进制乱码、无法定位行号且编码不兼容;需合理设置Scanner缓冲区、用strings.EqualFold或预转小写做匹配、正则须预编译,并注意文件编码问题。

别用 io.ReadAll 或 ioutil.ReadFile 直接加载整个文件——大日志或代码文件会 OOM,二进制内容会乱码,且无法定位行号。真要查关键词,就老实用 os.Open + bufio.Scanner 逐行扫。
为什么不能直接读全量再 strings.Contains
看似简单,但实际踩坑密集:
-
io.ReadAll对 100MB+ 日志文件会瞬间吃光内存,Linux 下直接被 OOM Killer 杀掉 - 二进制文件(如
.png、.pdf)读出来是乱码字节,strings.Contains可能误匹配或 panic - 找不到匹配行号——你得自己算换行符个数,Windows 和 Unix 换行符还不一样(
\r\nvs\n) - GBK 编码文件读出来就是一堆
,strings.Contains永远返回 false
bufio.Scanner 怎么设缓冲区才不崩
默认最大行长度 64KB,超长日志行(比如某条 JSON 日志塞了 2MB)会直接让 scanner.Scan() 返回 false,且 scanner.Err() 是 scanner: token too long。
必须提前扩容:
立即学习“go语言免费学习笔记(深入)”;
scanner := bufio.NewScanner(file) scanner.Buffer(make([]byte, 0, 1024*1024), 1024*1024) // 支持最长 1MB 行
- 第一个参数是初始底层数组,第二个是最大 token 长度(即单行上限)
- 设太大(如 100MB)也没用——Go runtime 会拒绝分配,报
runtime: out of memory - 设太小(如 1KB)会导致很多日志行被截断,匹配失败
- 建议按业务日志最长行预估,一般 1–2MB 足够覆盖绝大多数场景
大小写忽略和中文兼容怎么写才对
别在循环里反复调 strings.ToLower(line),既慢又可能出错(非 ASCII 字符在某些 locale 下行为异常)。
- 正确做法:用
strings.EqualFold判断相等,但它只支持完整字符串匹配,不支持子串 - 子串搜索仍得转小写,但只转一次:
keywordLower := strings.ToLower(keyword)放在扫描循环外 - 循环内写
strings.Contains(strings.ToLower(line), keywordLower)—— 中文、emoji、日文都安全,UTF-8 原生支持 - 如果 keyword 本身含 Unicode 格式字符(如零宽空格),
strings.ToLower不处理,此时应先 normalize(但 99% 场景不需要)
正则匹配必须预编译,否则性能断崖下跌
用户输入的正则若没预编译,每行都 regexp.Compile 一次,10 万行搜索从 200ms 涨到 10s+ 是常态。
- 启动时一次性编译:
re, err := regexp.Compile(<code>error.*timeout),检查err并友好提示语法错误 - 匹配用
re.MatchString(line),提取用re.FindStringSubmatch(返回[]byte,需转string) - 忽略大小写加
(?i)前缀:(?i)error.*timeout,别用re.FindAllString去遍历再转小写 - 注意 Go 正则基于 RE2,不支持
.*?非贪婪修饰符的完整语义,复杂嵌套模式慎用
真正容易被忽略的是编码问题:Go 的 os.Open 和 bufio.Scanner 完全不管文件是 UTF-8 还是 GBK,它只吐字节。搜不到不是代码写错了,而是你拿 UTF-8 解码器去读 GBK 文件——这种问题不会报错,只会静默失败。


















