simhash是一种语义敏感的指纹算法,通过分词、加权、向量合并、降维生成64位整数,使相似文本汉明距离小;而std::hash输入敏感,无法用于去重。

SimHash 是什么,为什么不能直接用 std::hash
SimHash 不是普通哈希,它生成的是「语义敏感」的指纹:相似文本产出的 64 位整数汉明距离很小。而 std::hash<:string></:string> 对输入极其敏感,"abc" 和 "abd" 哈希值完全无关,没法用于去重。SimHash 的核心在于分词 → 加权 → 向量合并 → 降维,C++ 标准库不提供现成实现。
手动实现 SimHash 的关键四步(C++17 可用)
不需要第三方库,但得自己写清楚每一步的取舍。重点不是“跑通”,而是控制精度和性能平衡:
- 分词建议用空格 + 标点切割,别上
std::regex——太慢;简单用std::stringstream或std::string_view::find_first_of切更稳 - 每个词用
std::hash<:string_view></:string_view>得到 64 位哈希值,再按词频或 TF-IDF 权重影响向量维度(初版可全设为 1) - 维护一个长度为 64 的整型数组
weight[64],对每个词哈希的每一位(bit 0 到 bit 63),若该位为 1 就+= weight,否则-= weight - 最后遍历
weight数组:正数则对应位设为 1,负数/零设为 0,组合成uint64_t返回
示例片段(省略分词):
uint64_t simhash(const std::vector<std::string_view>& words) {
int64_t weight[64] = {};
for (const auto& w : words) {
uint64_t h = std::hash<std::string_view>{}(w);
for (int i = 0; i < 64; ++i) {
bool bit = (h >> i) & 1;
weight[i] += bit ? 1 : -1;
}
}
uint64_t res = 0;
for (int i = 0; i < 64; ++i)
if (weight[i] > 0) res |= (1ULL << i);
return res;
}
汉明距离计算必须手写,别调 __builtin_popcountll 就完事
__builtin_popcountll(a ^ b) 确实快,但要注意:GCC/Clang 支持,MSVC 要换 std::popcount(C++20)或查表法。更重要的是——别在循环里反复算距离。实际去重时,应先用 std::unordered_set<uint64_t></uint64_t> 快速过滤汉明距离 > 3 的候选,再对小集合逐个计算精确距离。否则 O(n²) 一上来就崩。
立即学习“C++免费学习笔记(深入)”;
常见翻车点:分词太粗、权重没归一、64 位移位写错
最容易被忽略的是位操作细节:
- 用
1 计算掩码?i ≥ 31 就溢出 —— 必须写 <code>1ULL - 分词没去停用词、没转小写,导致 "The" 和 "the" 被当不同词,simhash 差距拉大
- 权重没做归一化(比如词频直接当权重),长文本天然压倒短文本,结果偏向长度而非语义
- 没考虑 Unicode:UTF-8 字符串直接切可能截断多字节,简单场景可先转
std::wstring或用 ICU,但多数日志/标题场景用 ASCII 分词已够用
SimHash 看似只是个哈希,但每个环节都在悄悄吃掉召回率。真正上线前,拿几十对人工标好的相似/不相似文本跑一遍,看汉明距离分布是否符合预期——这才是最不可跳过的验证。









