为什么在hashCode中使用素数?
我只是想知道为什么在类的hashCode()
方法中使用素数? 例如,当使用Eclipse生成我的hashCode()
方法时,总是使用素数31
:
public int hashCode() { final int prime = 31; //... }
参考文献:
这里是一个关于哈希码和哈希如何工作,我发现(C#,但概念是可转移的)的文章的一个很好的入门: Eric Lippert的准则和GetHashCode()
因为你想要你乘的数量和你插入的桶的数量有正交的素数分解。
假设有8个桶插入。 如果您用来乘以的数字是8的倍数,则插入的桶将只由最不重要的入口(根本不相乘的入口)确定。 类似的条目会相互冲突。 不适合散列函数。
31是一个足够大的素数,桶的数量不可能被它整除(实际上,现代Java HashMap实现将桶的数量保持为2的幂)。
素数被select为在散列桶之间最好地分配数据。 如果input的分布是随机且均匀分布的,那么散列码/模数的select就不重要了。 只有当input有一定的模式时才会产生影响。
处理内存位置时通常是这种情况。 例如,所有的32位整数都alignment到可被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
注意使用质数模量与非质数模数的近乎完美的分布。
但是,尽pipe上面的例子在很大程度上是人为devise的,但总体原则是,在处理input模式时 ,使用素数模数将产生最佳的分布。
对于什么是值得的, 有效的Java第二版放手围绕math问题,只是说,select31的原因是:
- 因为这是一个奇怪的素数,使用素数是“传统的”
- 这也是一个不到2的幂,这允许按位优化
以下是第9条中的完整引用:当您覆盖equals
时总是覆盖hashCode
:
值31被select,因为它是一个奇数的素数。 如果它是偶数并且倍增溢出,则信息将会丢失,因为乘以2相当于移位。 使用素数的好处不太清楚,但是是传统的。
31的一个很好的特性是乘法可以被replace为一个移位( §15.19 )和减法以获得更好的性能:
31 * i == (i << 5) - i
现代虚拟机自动进行这种优化。
虽然这个项目中的配方可以产生相当好的散列函数,但它不会产生最先进的散列函数,Java平台库也不会提供像1.6版本那样的散列函数。 编写这样的哈希函数是一个研究课题,最好留给math家和理论计算机科学家来完成。
也许稍后的平台版本将为其类和实用方法提供最先进的散列函数,以允许普通程序员构build这样的散列函数。 同时,本文所述的技术应该适用于大多数应用。
简而言之,可以说使用一个有许多除数的乘法器会导致更多的散列冲突 。 由于有效的哈希,我们希望尽量减less碰撞的次数,我们尝试使用一个更less的除数乘法器。 根据定义,素数恰好有两个明显的正数因子。
相关问题
- 来自一个字段的Java hashCode – 配方,以及使用Apache Commons Lang的构build器的示例
- 定义一个对象的哈希码作为所有类variables哈希码的和,乘,无论是不正确的?
- 绝对新手指南移位?
我听说select了31,以便编译器可以将乘法优化为左移5位然后减去该值。
这里有一个更接近源的引文 。
归结为:
- 31是素数,减less了碰撞
- 31产生良好的分配,与
- 速度合理的折衷
首先计算2 ^ 32( int
的大小)的散列值,所以你想要一个相对于2 ^ 32的素数(相对的素数意味着没有公因数)。 任何奇数都可以做到这一点。
那么对于一个给定的散列表,索引通常是根据散列值以散列表的大小为模来计算的,所以你需要一些与散列表大小相当的东西。 出于这个原因,散列表的大小通常被选为素数。 在Java的情况下,Sun的实现可以确保大小总是2的幂,所以奇数就足够了。 还有一些额外的散列键按摩来进一步限制冲突。
如果散列表和乘数具有共同的因子n
可能是在某些情况下,散列表中只有1 / n个条目将被使用。
它通常有助于在散列桶之间实现更均匀的数据传播,特别是对于低熵密钥。
31也特定于使用int作为散列数据types的Java HashMap。 因此最大容量为2 ^ 32。 使用更大的费马或梅森素数没有意义。