simhash 是语义敏感的指纹算法,需先将文件转为文本再分词加权生成指纹;直接对二进制操作会导致分词失效、海明距离无意义。

Simhash 是什么,为什么不能直接对文件二进制做 simhash
Simhash 不是哈希校验,也不是加密摘要;它本质是一个**语义敏感的指纹算法**,依赖文本分词 + 权重统计 + 降维投票。直接对 std::ifstream 读出的 raw bytes 做 simhash,结果完全不可比——因为哪怕一个字节偏移,所有分词全乱,海明距离失去意义。
所以真正可行的路径只有一条:把文件当作**文本内容处理**(或先提取文本),再走标准 simhash 流程。若文件是 PDF/DOCX,得先用 poppler 或 libreoffice --headless 转文本;纯文本文件则可跳过这步。
用 C++ 实现 Simhash 的核心三步(不依赖第三方库)
标准 simhash 算法共三步:分词 → 加权哈希 → 指纹生成。C++ 中没有内置分词器,需手动处理:
- 分词:按空白和标点切分,过滤停用词(如 "the", "a", "and"),保留长度 ≥2 的词;用
std::unordered_set存停用词表 - 加权哈希:对每个词调用
std::hash<:string>()</:string>得 64 位 hash,再根据词频(或 TF-IDF)决定该 hash 向量每个 bit 是 +weight 还是 -weight - 指纹生成:对 64 维向量每维求和,最后用符号函数转为 0/1:
(sum[i] >= 0) ? 1 : 0,拼成 uint64_t
示例关键片段:
uint64_t simhash(const std::vector<:string>& words, const std::vector<int>& weights) {
std::vector<int64_t> v(64, 0);
for (size_t i = 0; i {}(words[i]);
for (int b = 0; b > b) & 1) ? weights[i] : -weights[i];
}
}
uint64_t f = 0;
for (int b = 0; b = 0) f |= (1ULL
<h3>计算两个文件 simhash 的海明距离(注意整数类型和 bit 顺序)</h3>
<p>simhash 查重靠的是海明距离小 ≠ 内容相似,但距离 ≤3 通常意味着高度重复。C++ 中别用 <code>__builtin_popcountll</code> 前忘了 cast 到 <code>unsigned long long</code>——MSVC 和 GCC 对负数行为不一致。</p>
<ul>
<li>正确写法:<code>int dist = __builtin_popcountll(a ^ b)</code>,其中 <code>a</code> 和 <code>b</code> 都是 <code>uint64_t</code>
</li>
<li>Windows 下若编译报错,改用 <code>std::bitset(a ^ b).count()</code>(稍慢但跨平台)</li>
<li>注意:simhash 对短文本(file_size 字节的文件</li>
</ul>
<h3>实际查重时必须加的工程细节</h3>
<p>直接两两比对 O(n²) 肯定崩,尤其上万文件。必须引入局部敏感哈希(LSH)加速:</p>
<ul>
<li>把 64 位指纹拆成 4 组 16 位前缀,分别存入 4 个 <code>std::unordered_map<uint16_t std::vector>></uint16_t></code>(key 是前缀,value 是文件 ID 列表)</li>
<li>查重时,对目标文件的 simhash 取 4 个 16 位段,合并所有对应桶里的候选文件 ID,去重后逐一算海明距离</li>
<li>别省略归一化:英文要转小写、去标点;中文需先用 ICU 或 cppjieba 分词,不能按字切</li>
</ul>
<p>最容易被忽略的一点:simhash 对排版差异(换行、缩进、空格数量)鲁棒,但对同义词替换(如 "buy" ↔ "purchase")完全无感——这不是 bug,是设计使然。真要覆盖语义,得上 sentence-transformers,simhash 只管“表面重复”。</p></int64_t></int></:string>C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











