std::set直接存std::string不适合按行去重,因换行符处理不一致(如getline剥离n而>>不剥离)及空行/空白行语义模糊;应先用find_first/last_not_of清洗首尾空白(含r),再用unordered_set去重并配合vector保序。
std::set 直接存 std::string 不适合按行去重?因为文件读入的每行通常带换行符("n" 或 "rn"),而不同平台、不同读取方式(std::getline vs fgets)对换行符的处理不一致。直接丢进 std::set 会导致看似相同的两行被当作不同字符串——比如 "hellon" 和 "hello"(getline 自动剥离了 n,但若用 std::cin >> 就不会)。更麻烦的是,如果原始数据含空行或全空白行,它们也参与比较,但用户往往希望“逻辑空行”视为相同。
std::unordered_set 配合规范化处理?核心是统一清洗:先去除行首尾空白(包括 r),再判断是否为空。这样能兼容 Windows/Linux 换行差异,也避免空格干扰去重。
std::getline 是首选,它自动剥离 n(或 rn),返回不含换行符的字符串std::string::find_first_not_of(" trn") 和 find_last_not_of 截取有效内容;全空白则得到空串std::unordered_set<:string></:string>,O(1) 平均插入/查找,比 std::set 快std::vector 记录首次出现的行(配合 unordered_set::insert 的返回值判断)示例关键片段:
std::unordered_set<std::string> seen;std::vector<std::string> unique_lines;<p>std::string line;while (std::getline(std::cin, line)) {// 去首尾空白(含 r)auto start = line.find_first_not_of(" trn");if (start == std::string::npos) continue; // 空行跳过auto end = line.find_last_not_of(" trn");std::string clean = line.substr(start, end - start + 1);</p><pre class='brush:php;toolbar:false;'>if (seen.insert(clean).second) { // .second 为 true 表示新插入 unique_lines.push_back(std::move(clean));}
}
立即学习“C++免费学习笔记(深入)”;
纯内存方案在几百 MB 级文本上就容易爆内存(每个 std::string 有小开销,unordered_set 负载因子高时会 realloc)。真正快速的“按行去重”必须考虑外排或流式哈希。
std::unordered_set 存 std::string_view(C++17),但注意生命周期——必须确保源缓冲区不释放(比如整行读到 std::string 再转 view)std::hash 对长字符串碰撞率偏高,不推荐),哈希值存 std::unordered_set<uint64_t> 节省内存r 坑怎么绕?用 std::ifstream 默认以文本模式打开,Windows 下会自动将 rn 转成单个 n,所以 getline 返回的字符串不含 r。但如果你用二进制模式(std::ios::binary)读,或从管道/网络接收数据,r 就可能残留——此时清洗步骤里必须显式剔除 r,不能只靠 getline。
line.pop_back() 去 r:有些行末是 n,有些是 rn,有些甚至只有 r(老 Mac 格式)find_last_not_of(" trn") 安全截断,比条件判断更鲁棒实际写的时候,最容易被忽略的是:清洗逻辑必须和业务语义对齐。比如日志中 "error: timeout" 和 "error: timeout "(末尾空格)是否算重复?有些场景要算,有些要严格字节相等。别默认“去空格”,先想清楚需求再写清洗。