为什么在hashCode中使用素数?

Ian*_*las 160 java primes hashcode

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

public int hashCode() {
     final int prime = 31;
     //...
}
Run Code Online (Sandbox Code Playgroud)

参考文献:

这是关于Hashcode的一篇很好的入门文章和关于我如何找到哈希工作的文章(C#但概念是可转移的): Eric Lippert的GetHashCode指南和规则()

adv*_*ait 123

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

处理内存位置时经常会出现这种情况.例如,所有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
Run Code Online (Sandbox Code Playgroud)

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

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

  • 我们不是在讨论用于生成哈希码的乘数,而不是用于将这些哈希码排序到桶中的模数吗? (15认同)
  • 这种答案非常有用,因为它就像教别人如何钓鱼,而不是为他们捕捉一个.它帮助人们*看*和*理解*使用素数进行散列的基本原则......这是不规则地分配输入,因此一旦模数化它们就会统一地落入桶中:). (8认同)
  • 同样的原则.就I/O而言,哈希提供给哈希表的模运算.我认为关键在于,如果你乘以质数,你将获得更多随机分布的输入到模数甚至无关紧要的点.由于散列函数可以更好地分配输入,使它们更不规则,因此不管用于将它们放入存储桶的模数,它们都不太可能发生冲突. (3认同)

ILM*_*tan 94

因为您需要乘以的数字和要插入的桶数以具有正交的素数因子分解.

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

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

  • 那么基于哈希表实现者知道31在哈希码中常用的假设来选择31? (11认同)
  • 然后,乘以31的散列函数将执行非最佳.但是,考虑到作为乘法器的共同点31,我会考虑这样的哈希表实现设计不佳. (9认同)
  • 换句话说......我们需要了解一组输入值和集合的规律性,以便编写一个旨在去除它们的规则性的函数,因此集合中的值不会相同哈希桶.乘以质数的乘法/除法/模数实现了这种影响,因为如果你有一个带有X项的LOOP并且你在循环中跳过Y空格,那么你将永远不会返回到同一个点,直到X成为Y的因子由于X通常是偶数或2的幂,那么你需要Y为素数,因此X + X + X ...不是Y的因子,所以31岁!:/ (8认同)
  • 基于大多数实现具有相对小的素数的因子分解的想法来选择图31.通常是2s,3s和5s.它可能从10开始,当它太满时会增长3倍.尺寸很少完全随机.即使它是,30/31也不是很好的同步哈希算法的几率.如其他人所述,它也可能很容易计算. (3认同)
  • @FrankQ.这是模运算的本质.`(x*8 + y)%8 =(x*8)%8 + y%8 = 0 + y%8 = y%8` (3认同)

pol*_*nts 24

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

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

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

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

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

 31 * i == (i << 5) - i
Run Code Online (Sandbox Code Playgroud)

现代VM自动执行此类优化.


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

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

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

相关问题

  • 呃,但是有许多合适的*素数*是*2 ^ n + 1*(所谓的*费马素数*),即`3,5,17,257,65537`或*2 ^ n - 1*(*Mersenne primes*):`3,7,31,127,8191,131071,524287,2147483647`.然而,选择"31"(而不是"127"). (4认同)
  • _"因为它是一个奇数素数"_ ...只有一个偶数素数:P (3认同)

Ste*_*Kuo 5

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

  • 共同意见无关紧要. (4认同)
  • @Grizzly,它比乘法更快。IMul​​ 在任何现代 CPU 上的最小延迟为 3 个周期。(参见agnerfog的手册) `mov reg1, reg2-shl reg1,5-sub reg1,reg2` 可以在2个周期内执行。(mov只是重命名并且需要0个周期)。 (3认同)