悠悠楠杉
Golang如何实现文件内容搜索与统计
核心需求与设计思路
假设我们有一个项目目录,包含数百个.log或.txt文件,需要查找所有包含“error”关键字的行,并统计其出现频率。理想情况下,程序应支持指定目录、过滤文件类型、忽略大小写,并能输出匹配行数及文件分布。
为实现这一目标,我们将模块化设计:使用filepath.Walk遍历目录,os.Open读取文件,bufio.Scanner逐行解析,配合strings.Contains或正则表达式进行匹配,最后通过sync.Map安全地统计结果。
文件遍历与筛选
首先,定义一个结构体承载搜索参数:
go
type SearchConfig struct {
RootDir string
Keyword string
CaseSensitive bool
FileExt string // 如 ".log"
}
利用filepath.Walk递归进入子目录。在遍历过程中,判断文件扩展名是否匹配,跳过目录和不相关文件:
go
err := filepath.Walk(config.RootDir, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
if info.IsDir() || !strings.HasSuffix(info.Name(), config.FileExt) {
return nil
}
// 处理单个文件
processFile(path, config, results)
return nil
})
这里results是一个线程安全的映射,用于收集每个文件中的匹配行。
高效读取与内容匹配
对每个符合条件的文件,使用os.Open打开并交由bufio.Scanner按行处理。这种方式内存友好,适合大文件:
go
file, err := os.Open(filePath)
if err != nil {
log.Printf("无法打开文件 %s: %v", filePath, err)
return
}
defer file.Close()
scanner := bufio.NewScanner(file)
lineNum := 0
for scanner.Scan() {
lineNum++
line := scanner.Text()
match := false
if config.CaseSensitive {
match = strings.Contains(line, config.Keyword)
} else {
match = strings.Contains(
strings.ToLower(line),
strings.ToLower(config.Keyword))
}
if match {
// 安全写入共享map
results.Store(filePath, append(getLines(results, filePath), LineMatch{
Line: lineNum, Content: line}))
}
}
其中LineMatch结构体记录行号和内容,便于后续展示。
并发优化与结果汇总
为提升性能,可将文件处理放入goroutine。由于多个协程会同时写入results,必须使用sync.Map保证并发安全。主协程通过WaitGroup等待所有任务完成:
go
var wg sync.WaitGroup
for _, file := range files {
wg.Add(1)
go func(f string) {
defer wg.Done()
processFile(f, config, results)
}(file)
}
wg.Wait()
搜索结束后,遍历results统计总匹配行数、涉及文件数,并可进一步分析高频关键词(如结合map[string]int统计词频)。
实际应用场景扩展
该工具不仅适用于日志排查,还可用于代码审计(查找敏感函数调用)、数据清洗(提取特定格式内容)等场景。通过引入正则表达式支持,能实现更复杂的模式匹配;添加JSON输出选项,则便于与其他系统集成。
整个实现不足200行代码,却具备良好的扩展性与实用性。Golang的静态编译特性使其可直接部署在服务器上,无需依赖环境,真正做到了“一次编写,随处运行”。

