go读文件去重核心是跳过已见行,首选map[string]bool边读边记;需trimspace防换行符干扰;大文件宜用sort-u或awk;空行注释行应预过滤。

用 map[string]bool 快速判断重复行是否已存在
Go 读文件去重,核心不是“删重复”,而是“跳过已见过的行”。最轻量、最常用的做法是边读边用 map[string]bool 记录出现过的行。注意:map 的 key 是整行字符串(含换行符),所以建议用 strings.TrimSpace() 去掉首尾空白再判断,否则 "abc" 和 "abc\n" 会被视为不同行。
常见错误是直接拿 bufio.Scanner.Text() 结果当 key,没处理末尾换行或空格,导致逻辑上相同的行被重复保留。
- 如果业务要求严格区分大小写和空白,跳过
TrimSpace,但务必确认这是你真正需要的 - 大文件(>500MB)慎用此法——所有唯一行都进内存,可能 OOM
- 行末换行符统一用
\n处理即可,Windows 的\r\n会被Scanner自动转成\n
用 sort.Strings + 遍历实现稳定去重(保留首次出现顺序)
若需输出去重后仍保持原始首次出现的顺序(而非字典序),不能依赖 map 的遍历顺序(Go 1.12+ 虽有伪随机化保护,但不保证输入顺序),得额外维护一个切片记录“首次见到的行”。更稳妥的替代方案是:先读入所有行([]string),调用 sort.Strings() 排序,再单次遍历跳过相邻重复项。这样既稳定,又避免 map 分配开销。
示例关键逻辑:
sort.Strings(lines)
for i, line := range lines {
if i == 0 || line != lines[i-1] {
uniqueLines = append(uniqueLines, line)
}
}
- 此法会改变原始顺序,仅适合允许重排序的场景(如生成索引、统计前处理)
- 排序本身是 O(n log n),比 map 的 O(n) 慢,但内存峰值更低(无需 map 结构体开销)
- 注意:排序前必须确保每行已标准化(如统一 Trim、大小写转换),否则
"Apple"和"apple"不会被识别为重复
用 os/exec 调用系统 sort -u 处理超大文件
当文件远超可用内存(例如数 GB 日志),硬抗在 Go 里读取+去重极易触发 GC 压力或直接 panic。此时应让 OS 工具链干活:sort -u 是 POSIX 标准命令,流式处理、内存可控、结果稳定。
Go 中调用方式简洁:
cmd := exec.Command("sort", "-u", "input.txt")
out, err := cmd.Output()
- 注意:
sort -u默认按字典序排,且会修改行序;如需保持原序,得用awk '!seen[$0]++' input.txt替代 - Windows 下无原生
sort -u,需依赖 WSL、Git Bash 或改用 PowerShell 的Get-Content | Sort-Object -Unique(性能较差) - 务必检查
err和cmd.ProcessState.ExitCode(),sort在磁盘满或权限不足时会静默失败
去重时忽略空行和注释行的典型处理模式
实际项目中(如配置文件、脚本列表),常需跳过空行、纯空白行、以 # 或 // 开头的注释行。这不是“去重”本身的问题,但和去重逻辑强耦合——如果把这些行也塞进 map,会污染去重结果,还可能掩盖真实重复问题。
推荐在读取后立即过滤:
line = strings.TrimSpace(line)
if line == "" || strings.HasPrefix(line, "#") || strings.HasPrefix(line, "//") {
continue
}
- 不要在去重后再过滤,否则
map已记录空行,后续相同空行仍会被跳过,造成误判 - 正则匹配注释(如支持
/* */)成本高,简单场景用strings.HasPrefix足够且零分配 - 注意:某些格式(如 INI)允许行内注释(
key=value # comment),此时需先分割再 Trim,不能整行跳过
实际去重逻辑的健壮性,往往卡在“怎么定义一行”——是原始字节流?Trim 后?标准化编码后?这个问题没有银弹,得根据你的数据源和下游消费方决定。别默认用 Text() 就完事,多打一行 fmt.Printf("%q\n", line) 看看真实内容。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











