如果我们有以下优先级(按照顺序),哪种哈希算法是最好的:
- 最小化哈希冲突
- 性能
它不一定要安全。基本上,我正在尝试创建一个基于某些对象的属性组合的索引。所有属性都是字符串。
如果有任何关于C#实现的参考资料,将不胜感激。
如果我们有以下优先级(按照顺序),哪种哈希算法是最好的:
它不一定要安全。基本上,我正在尝试创建一个基于某些对象的属性组合的索引。所有属性都是字符串。
如果有任何关于C#实现的参考资料,将不胜感激。
忘掉"最好"这个词。无论任何人想出什么哈希算法,除非你有一个非常有限的数据集需要被哈希,否则每个平均性能表现出色的算法都可能在只输入正确(或从您的角度来看是“错误”)的数据时变得完全无用。
与其浪费太多时间考虑如何使哈希更加碰撞自由而不使用太多CPU时间,我宁愿开始思考“如何使碰撞问题不那么棘手”。例如,如果每个哈希桶实际上是一个表格,并且所有在此表格中发生冲突的字符串都按字母顺序排序,则可以使用二分查找(仅为O(log n))在桶表中进行搜索。这意味着,即使每秒哈希桶有4个碰撞,您的代码仍将具有良好的性能(与无碰撞表格相比会稍慢一些,但不会太慢)。这里的一个很大的优点是,如果您的表足够大并且您的哈希不太简单,则导致相同哈希值的两个字符串通常看起来完全不同(因此二分搜索平均只需比较一或两个字符即可停止比较字符串;使每次比较非常快)。
实际上,我之前遇到过一种情况,直接在排序后的表格中使用二分搜索进行搜索比哈希更快!尽管我的哈希算法很简单,但要将这些值哈希需要相当长的时间。性能测试表明,只有当我有超过大约700-800个条目时,哈希才比二分搜索快。但是,由于该表永远不会变得大于256个条目,并且平均表格低于10个条目,基准测试清楚地表明,在每个系统和每个CPU上,二分搜索都更快。在这里,通常已经比较数据的第一个字节就足以导致下一个二分搜索迭代(因为第一或两个字节中使用的数据已经非常不同),这被证明是一个很大的优势。
因此,总结一下:我会选择一个不会在平均情况下引起太多碰撞且速度较快的良好哈希算法(即使出现一些更多的碰撞,只要速度很快也可以接受!),并尽可能地优化我的代码,以便在出现碰撞时获得最小的性能损失(而且它们肯定会发生!除非您的哈希空间至少与您的数据空间一样大或更大,并且您可以将唯一的哈希值映射到每个可能的数据集)。
正如Nigel Campbell所指出的那样,没有所谓的“最佳”哈希函数,因为它取决于你要哈希的数据特征以及是否需要具有密码质量的哈希。
话虽如此,以下是一些提示:
由于输入哈希的项只是一组字符串,您可以简单地合并每个单独字符串的哈希码。我见过以下伪代码的建议来执行此操作,但我不知道任何特定的分析:
int hashCode = 0;
foreach (string s in propertiesToHash) {
hashCode = 31*hashCode + s.GetHashCode();
}
根据这篇文章,System.Web有一个内部方法,可以使用哈希码进行组合。combinedHash = ((combinedHash << 5) + combinedHash) ^ nextObj.GetHashCode();
我也看到过一些简单地使用异或操作合并哈希码的代码,但我认为这是一个糟糕的想法(虽然我没有分析来支持这个观点)。如果同样的字符串以不同的顺序被哈希,则最终会产生冲突。
我已经成功地使用了FNV哈希函数:http://www.isthe.com/chongo/tech/comp/fnv/
Paul Hsieh有一篇不错的文章:http://www.azillionmonkeys.com/qed/hash.html
Bob Jenkins还写了一篇不错的文章,最初发表在1997年的Doctor Dobb's Journal上(链接中的文章进行了更新):http://burtleburtle.net/bob/hash/doobs.html
没有一个单一的最佳哈希算法。如果您有已知的输入域,可以使用完美哈希生成器(例如gperf)生成一个哈希算法,该算法将在该特定输入集上获得100%的成功率。否则,对于这个问题,没有一个“正确”的答案。
/**
* Computes the hashcode for this String. This is done with int arithmetic,
* where ** represents exponentiation, by this formula:<br>
* <code>s[0]*31**(n-1) + s[1]*31**(n-2) + ... + s[n-1]</code>.
*
* @return hashcode value of this String
*/
public int hashCode()
{
if (cachedHashCode != 0)
return cachedHashCode;
// Compute the hash code using a local variable to be reentrant.
int hashCode = 0;
int limit = count + offset;
for (int i = offset; i < limit; i++)
hashCode = hashCode * 31 + value[i];
return cachedHashCode = hashCode;
}
这里是布谷鸟哈希。
查找只需要检查哈希表中的两个位置,在最坏情况下需要常数时间(参见大O符号)。这与许多其他哈希表算法不同,后者可能没有在查找时具有恒定的最坏情况时间界限。
我认为这符合您关于冲突和性能的标准。看起来这种类型的哈希表只能达到49%的填充率。