推荐用 os.open + bufio.scanner 逐行读取文本文件搜索,避免 oom 和乱码;需 defer 关闭文件、预调大缓冲区防超长行、编码不同时先转 utf-8;大小写忽略应提前转小写或用 equalfold;正则须预编译;目录遍历用 filepath.walkdir 并限流。

Go 语言做文件内容搜索,别直接 ioutil.ReadFile 加 strings.Contains —— 大文件会 OOM,二进制文件会乱码,目录遍历容易卡死或触发 “too many open files”。
用 os.Open + bufio.Scanner 逐行读取最稳
这是绝大多数文本搜索场景的默认起点。它不把整个文件加载进内存,而是按行流式处理,天然防爆内存。
- 必须在
for scanner.Scan()外面加defer f.Close(),否则文件句柄泄漏,跑几次就报too many open files - 每行用
scanner.Text()获取,不是scanner.Bytes()—— 后者返回[]byte,要转string才能给strings.Contains用 - 行长超长(比如日志里某行几 MB)会导致
scanner.Scan()失败,默认最大 64KB;可调大缓冲区:scanner.Buffer(make([]byte, 0, 1024*1024), 1024*1024) - 如果文件是 GBK/GB2312 编码,
bufio.Scanner会读错或截断 —— 此时不能硬来,得先用golang.org/x/text/encoding转成 UTF-8 再扫
关键字搜索要不要忽略大小写?别在循环里反复 strings.ToLower
区分大小写是默认行为,但用户常要 -i 开关。错误做法是每行都调 strings.ToLower(line) 和 strings.ToLower(keyword);正确做法是提前转好,只转一次。
Go语言(Golang)1.26.0
下载
Go语言(Golang)1.26.0版本提供 Go 官方 Windows amd64 MSI 安装包下载入口,版本号 1.26.0,可用于旧项目维护、兼容性测试和指定版本开发环境配置。
- 安全写法:
keywordLower := strings.ToLower(keyword)放在循环外,循环内只做strings.Contains(strings.ToLower(line), keywordLower) - 更高效写法:用
strings.EqualFold替代strings.Contains的大小写判断逻辑 —— 但它只判相等,不支持子串;所以仍得先转小写再Contains - 注意:
strings.Contains是字节级匹配,遇到带 BOM 的 UTF-8 文件不会出错,但遇到混合编码(如部分 GBK 字节混入)会崩,这类文件建议先跳过或预检
正则匹配必须预编译 regexp.Compile,且注意语法边界
用户输个 d{3}-d{2}-d{4} 就想搜身份证?没问题,但别在每行都 regexp.Compile —— 它开销大,还可能 panic。
- 启动时一次性编译:
re, err := regexp.Compile(<code>\d{3}-\d{2}-\d{4}),检查err并友好提示(比如 “正则语法错误:missing closing ]”) - 匹配用
re.MatchString(line),提取用re.FindStringSubmatch(返回[]byte,记得转string) - Go 的
regexp基于 RE2,不支持.*?非贪婪、不支持(? 先行断言(lookbehind)—— Go 1.22 前完全不支持,1.23+ 仅支持固定长度 - 想忽略大小写?写成
(?i)<code>pattern,别靠外面转字符串,RE2 本身支持
搜整个目录必须用 filepath.WalkDir,并发要限流
filepath.WalkDir 是 Go 1.16+ 的事实标准,比老版 filepath.Walk 快 20%–40%,且能自动跳过权限拒绝目录(返回 fs.SkipDir 即可)。
- 别用
entry.Info()拿文件大小或时间 —— 它会触发一次stat系统调用;只查名字就用entry.Name(),零开销 - 并发搜索时,用
sem := make(chan struct{}, 10)控制 goroutine 数量(10 是常见安全值),每个 goroutine 开始前sem ,结束后 <code> - 跳过二进制文件:检查魔数(magic number)比只看扩展名靠谱,可用
file.Header前 512 字节比对,或简单用bytes.IndexByte查
