用 std::unordered_set 而不是 std::set 是因平均 o(1) 插入/查找优于 std::set 的 o(log n),尤其对几千行以上长文本;但需注意哈希冲突、windows 换行符残留 '\r'、顺序保留需配合 vector、建议预分配容量并 move 优化。

为什么用 std::unordered_set 而不是 std::set?
按行去重的核心开销在插入和查找,std::unordered_set 平均是 O(1),而 std::set 是 O(log n)。对几千行以上的文本,性能差距明显——尤其当每行长度不短时,std::set 的树节点构造+字符串比较会拖慢整体速度。
但要注意:std::unordered_set<:string></:string> 默认哈希函数对长字符串可能产生较多冲突;若行内容高度相似(比如只有末尾数字不同),建议自定义哈希或改用 std::string_view + std::unordered_set 配合 std::string_view 哈希(C++17 起支持)。
- 必须启用 C++17 或更高标准才能直接用
std::hash<:string_view></:string_view> -
std::string_view不拥有内存,读取文件时需确保原始缓冲区生命周期覆盖整个去重过程 - 若逐行读取
std::getline,拿到的是std::string,转成std::string_view无额外拷贝,但不能存入持久容器(除非你把所有行先存进一个std::vector<:string></:string>再建 view)
如何避免重复读取和内存浪费?
常见错误是边读边插、再遍历 set 输出——这会导致行序丢失,且无法保留首次出现的位置。真正“按行去重”通常指:保留每行第一次出现的顺序,后续重复行跳过。
所以不能只靠 std::unordered_set,得配合一个容器记录顺序。推荐组合:std::unordered_set<:string></:string> 做查重 + std::vector<:string></:string> 存结果。
- 每次
std::getline后,检查该行是否已在seen中;不在则插入seen并 push 到result - 不要用
std::unordered_set直接遍历输出——它不保序,且迭代器遍历本身比 vector 慢 - 如果输入来自大文件,考虑用
std::ios_base::sync_with_stdio(false)和cin.tie(nullptr)加速读取
Windows 换行符("\r\n")导致去重失效怎么办?
std::getline 默认按 '\n' 切割,但在 Windows 文本文件中,行尾可能是 "\r\n",此时 getline 会把 '\r' 留在字符串末尾,造成看似相同的两行实际为 "abc" 和 "abc\r",被当成不同字符串处理。
最稳妥做法是在插入前统一 trim '\r':
std::string line;
while (std::getline(file, line)) {
if (!line.empty() && line.back() == '\r') {
line.pop_back();
}
if (seen.insert(line).second) {
result.push_back(line);
}
}
- 不要依赖
std::regex或boost::trim——太重,纯pop_back条件判断足够快 - 如果文件可能含 Unix、Mac、Windows 混合换行,可扩展为 trim 末尾的
'\r'和'\n',但注意getline已剥离'\n',一般只需处理'\r' - 某些编辑器保存为 “UTF-8 with BOM”,首行开头会有
"\xef\xbb\xbf",如需兼容,应在读取后检查并跳过
内存峰值高?试试 reserve + move 优化
对百万行级输入,频繁 push_back 和 unordered_set 重建桶可能引发多次 realloc。提前预估规模能显著减少重哈希次数。
- 若知道大概行数 N,调用
seen.reserve(N)和result.reserve(N) - 插入时用
seen.insert(std::move(line)),避免二次拷贝(前提是 line 后续不再使用) - 如果只是输出去重结果而不存,可省掉
resultvector,直接std::cout ——但注意这仍要保留 <code>seen查重状态 - 极端场景(如单行超 1MB),考虑用
std::string_view+ 外部 buffer(例如 mmap 文件),但需手动管理生命周期,容易出错
真正难的不是选 set 还是 unordered_set,而是换行符处理、内存布局和 move 语义的组合使用——漏掉任一环节,都可能让“快速”变成“看起来快”。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











