为什么在hashCode中使用素数?

时间:2010-08-31 20:46:24

标签: java hashcode primes

我只是想知道为什么在类的hashCode()方法中使用素数?例如,当使用Eclipse生成我的hashCode()方法时,始终使用素数31

public int hashCode() {
     final int prime = 31;
     //...
}

参考文献:

这是关于Hashcode的一篇很好的入门文章和关于我如何找到哈希工作的文章(C#,但概念是可转移的): Eric Lippert's Guidelines and rules for GetHashCode()

9 个答案:

答案 0 :(得分:119)

选择素数以在散列桶之间最佳地分配数据。如果输入的分布是随机且均匀分布的,则哈希码/模数的选择无关紧要。只有当输入存在某种模式时,它才会产生影响。

处理内存位置时经常会出现这种情况。例如,所有32位整数都与可被4整除的地址对齐。请查看下表,以显示使用素数与非素数模数的效果:

Input       Modulo 8    Modulo 7
0           0           0
4           4           4
8           0           1
12          4           5
16          0           2
20          4           6
24          0           3
28          4           0

注意使用质数模量与非素数模量时几乎完美的分布。

然而,虽然上面的例子很大程度上是设计的,但一般原则是当处理输入模式时,使用素数模数将产生最佳分布。

答案 1 :(得分:93)

因为你想要你所乘的数字和你要插入的桶数有正交的素数分解。

假设有8个桶要插入。如果您用来乘以的数字是8的某个倍数,那么插入的数据桶将仅由最不重要的条目(根本不相乘的条目)确定。类似的条目将发生冲突。对哈希函数不好。

31是足够大的素数,桶的数量不可能被它整除(事实上,现代的Java HashMap实现将桶的数量保持为2的幂)。

答案 2 :(得分:23)

对于它的价值, Effective Java 2nd Edition 可以放弃数学问题,只是说选择31的原因是:

  • 因为它是一个奇数素数,而且使用素数是“传统的”
  • 它也是一个小于2的幂,它允许按位优化

这是完整的引用,来自第9项:覆盖hashCode 时始终覆盖equals

  

选择值31是因为它是一个奇数素数。如果它是偶数并且乘法溢出,则信息将丢失,因为乘以2相当于移位。使用素数的优势不太明显,但它是传统的。

     

31的一个很好的属性是乘法可以用移位(§15.19)和减法代替,以获得更好的性能:

 31 * i == (i << 5) - i
     

现代虚拟机会自动执行此类优化。

     
     

虽然这个项目中的配方产生了相当好的散列函数,但它不会产生最先进的散列函数,Java平台库也不提供1.6版本的散列函数。编写这样的哈希函数是一个研究课题,最好留给数学家和理论计算机科学家。

     

平台的后续版本可能会为其类和实用程序方法提供最先进的哈希函数,以允许普通程序员构建此类哈希函数。与此同时,本项目中描述的技术应该适用于大多数应用程序。

相当简单,可以说使用具有多个除数的乘数将导致更多hash collisions。由于有效散列我们希望最小化碰撞次数,因此我们尝试使用具有较少除数的乘数。根据定义,素数具有两个截然不同的正分数。

相关问题

答案 3 :(得分:5)

我听说31被选中以便编译器可以优化乘法到左移5位然后减去该值。

答案 4 :(得分:2)

这是citation离源头更近一点。

归结为:

  • 31是素数,减少了碰撞
  • 31产生了良好的分布,
  • 合理的速度权衡

答案 5 :(得分:2)

首先计算哈希值模2 ^ 32(int的大小),所以你想要一些相对素数到2 ^ 32(相对素数意味着没有公约数)。任何奇数都可以做到。

然后,对于给定的哈希表,索引通常是根据散列表的大小模数的散列值计算的,因此您需要的东西相对于散列表的大小是相对的。由于这个原因,通常会选择哈希表的大小作为素数。在Java的情况下,Sun实现确保大小总是2的幂,所以奇数也足够了。还有一些额外的哈希键按摩来进一步限制冲突。

如果哈希表和乘数具有公因子n,那么效果可能是在某些情况下只会使用哈希表中的1 / n个条目。

答案 6 :(得分:0)

它通常有助于在散列桶之间实现更均匀的数据传播,尤其是对于低熵密钥。

答案 7 :(得分:0)

31也特定于Java HashMap,它使用int作为哈希数据类型。因此最大容量为2 ^ 32。使用较大的Fermat或Mersenne素数是没有意义的。

答案 8 :(得分:0)

之所以使用质数,是为了在数据表现出某些特定模式时最大程度地减少冲突。

首先,如果数据是随机的,则不需要质数,您可以对任何数字进行模运算,并且对于每个可能的模数值,您将拥有相同数量的碰撞。

但是,当数据不是随机的时,就会发生奇怪的事情。例如,考虑始终为10的倍数的数字数据。

如果使用mod 4,我们会发现:

10 mod 4 = 2

20 mod 4 = 0

30 mod 4 = 2

40 mod 4 = 0

50 mod 4 = 2

因此,从模数(0,1,2,3)的3个可能值中,只有0和2会发生碰撞,这很糟糕。

如果我们使用质数,例如7:

10 mod 7 = 3

20 mod 7 = 6

30 mod 7 = 2

40 mod 7 = 4

50 mod 7 = 1

我们还注意到5不是一个好选择,但5是质数,原因是我们所有的键都是5的倍数。这意味着我们必须选择一个不除键的质数,选择一个大素数通常就足够了。

因此,在重复方面出现错误的原因是使用质数的原因是在散列函数的冲突分布中抵消键中模式的影响。