simhash在c#中需自行实现或选用可靠库,因其是语义敏感的局部敏感哈希,非加密哈希;使用时须规范文本预处理、分词、归一化,并依场景动态设定汉明距离阈值。

Simhash 在 C# 里不是内置算法,得自己实现或找靠谱库
原生 System.Security.Cryptography 不提供 Simhash,它和 MD5、SHA256 这类加密哈希不同——Simhash 是语义敏感的局部敏感哈希(LSH),目标是让相似文本产出汉明距离小的指纹。直接搜 SimHash 类名会撞上一堆半成品或错用 BitArray 的老代码。
推荐用 SimMetrics.Net 或更轻量的 Text.Similarity(NuGet 可搜),但注意后者默认只做词频向量,不带 Simhash;真要 Simhash,目前最稳的是 Google.Similarity(已归档但代码干净)或自己实现核心逻辑。
实操建议:
- 别用网上抄来的“10 行 Simhash”——多数漏了分词归一化、权重计算、降维截断,结果不可复现
- 如果只是比两个文件是否高度重复,
Simhash.Compute+HammingDistance就够用;超过 1000 个文件要做聚类,就得建倒排索引或用MinHash配合 LSH 库 - 中文必须先分词(如用
Microsoft.ML.Tokenizers或Stanford.NLP.Chinese),直接按字或空格切,Simhash 效果接近随机
文件读取时的编码和换行处理会悄悄毁掉 Simhash 结果
Simhash 对输入极其敏感:多一个 \r、少一个标点、BOM 头没 strip,指纹就完全不一样。而 C# 的 File.ReadAllText 默认用 UTF-8 但不处理 BOM,File.ReadAllLines 会吃掉 \r\n 却保留 \n,导致同一份文件在 Windows/macOS 上算出不同指纹。
正确做法是统一归一化:
- 用
File.ReadAllBytes读原始字节,再用Encoding.UTF8.GetString(bytes).Normalize()强制 NFC 规范化 - 手动替换所有换行符为
\n:text.Replace("\r\n", "\n").Replace("\r", "\n") - 去掉首尾空白和常见冗余(如 HTML 注释、Markdown 元数据块),否则
---\ntitle: xxx\n---这种头信息会让技术文档和博客文章指纹严重偏移
Simhash 指纹长度和分词粒度直接影响查准率
标准 Simhash 用 64 位指纹,但实际中 32 位对小文件(
常见错误是把整文件当一个字符串喂给 Simhash——它根本没法感知语义,只会把长尾噪声(日志时间戳、随机 ID)当成特征。
必须分层处理:
- 大文件(>100KB)先按段落切(正则
"\n\s*\n"),每段单独算 Simhash,再合并(如异或或加权平均) - 代码文件建议按函数/类粒度提取(用
Microsoft.CodeAnalysis解析 AST),比纯文本分词准确 3 倍以上 -
shingleSize = 5(即每 5 个词滑动窗口)是中文通用起点;英文可试3;配置文件或日志用1(单 token 权重)更稳
汉明距离阈值不是固定值,得按场景调
说“汉明距离 ≤3 就算相似”是典型想当然。64 位下距离 3 意味着 95% 位相同,但实际文件只要改两段代码、加个注释,就可能跳到 8–12。硬设阈值会导致漏判或误报。
真实项目里得动态定界:
- 对源码比对,用
distance (即允许最多 6.25% 差异) - 对日志或文本报告,先统计样本集的距离分布,取 P90 分位数当阈值
- 永远配合其他信号交叉验证:比如
LevenshteinRatio> 0.7 且Simhash.Distance
最常被忽略的一点:Simhash 本身不区分“相似”和“包含”。一份完整文档和它的摘要,指纹距离可能很小,但业务上它们不是重复内容——得加一层子串检测或 TF-IDF 余弦相似度兜底。










