如何选择素数来计算哈希码?

Joo*_*ken 7 .net c# hash primes gethashcode

这个问题遵循Jon Skeet在这个问题上给出的答案:" 覆盖System.Object.GetHashCode的最佳算法是什么? ".要计算哈希码,请使用以下算法:

public override int GetHashCode()
{
    unchecked // Overflow is fine, just wrap
    {
        int hash = 17;
        // Suitable nullity checks etc, of course :)
        hash = hash * 23 + field1.GetHashCode();
        hash = hash * 23 + field2.GetHashCode();
        hash = hash * 23 + field3.GetHashCode();
        return hash;
    }
}
Run Code Online (Sandbox Code Playgroud)

我不明白为什么选择数字17和23.我们为什么不选3和5?这也是素数.有人可以解释一下最好的素数是什么以及为什么?

小智 10

您链接到的答案的评论已经简要地尝试解释为什么17并且23不是在这里使用的好素数.

许多使用哈希码的.NET类在存储桶中存储元素.假设有三个桶.然后,所有具有哈希码0,3,6,9 ......的对象都存储在桶0中.所有具有哈希码1,4,7,10 ......的对象都存储在桶1中.所有带桶2的对象,5,8,11 ......存放在桶2中.

现在假设你的GetHashCode()用途hash = hash * 3 + field3.GetHashCode();.这意味着除非hash足够大以使乘法环绕,在具有三个桶的散列集中,对象最终将依赖于哪个桶field3.

对于跨桶的物体分布不均匀,HashSet<T>不能给出良好的性能.

您需要一个与所有可能数量的桶共同构成的因子.由于相同的原因,桶本身的数量将是素数,因此如果您的因子是素数,唯一的风险是它等于桶的数量.

.NET使用允许数量的桶的固定列表:

public static readonly int[] primes = {
    3, 7, 11, 17, 23, 29, 37, 47, 59, 71, 89, 107, 131, 163, 197, 239, 293, 353, 431, 521, 631, 761, 919,
    1103, 1327, 1597, 1931, 2333, 2801, 3371, 4049, 4861, 5839, 7013, 8419, 10103, 12143, 14591,
    17519, 21023, 25229, 30293, 36353, 43627, 52361, 62851, 75431, 90523, 108631, 130363, 156437,
    187751, 225307, 270371, 324449, 389357, 467237, 560689, 672827, 807403, 968897, 1162687, 1395263,
    1674319, 2009191, 2411033, 2893249, 3471899, 4166287, 4999559, 5999471, 7199369};
Run Code Online (Sandbox Code Playgroud)

您的因素应该是.NET不使用的因素,而其他自定义实现同样不太可能使用.这意味着23一个不好的因素.31使用.NET自己的容器可能没问题,但对于自定义实现可能同样糟糕.

同时,它不应该太低,以至于它会为常见用途提供大量碰撞.这是一个风险35:假设你有一个自定义Tuple<int, int>有很多小的整数执行.请记住,int.GetHashCode()只返回int自己.假设您的乘法因子是3.这意味着(0, 9),(1, 6),(2, 3)(3, 0)都给予相同的哈希码.

使用足够大的素数可以避免这两个问题,正如Jon Skeet在他的回答中所引用的评论中指出的那样:

编辑:正如评论中所指出的,你可能会发现最好选择一个大的素数来代替.显然486187739很好......

曾几何时,用于乘法的大质数可能是坏的,因为大整数的乘法足够慢,性能差异是明显的.31在这种情况下,乘法将是好的,因为它可以实现为x * 31=> x * 32 - x=> (x << 5) - x.然而,如今,乘法不太可能导致任何性能问题,然后,一般来说,越大越好.

  • @usr这表明大素数*不是*可行的方法,并且可能会产生一个与我的观点不同的有趣答案。如果你有感觉,就写下来。:) (2认同)