繁体   English   中英

为什么在 hashCode 中使用质数?

[英]Why use a prime number in hashCode?

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

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

参考资料:

这是我发现的关于 Hashcode 和散列如何工作的一篇很好的入门书(C#,但概念是可转移的): Eric Lippert 的 GetHashCode() 指南和规则

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

处理内存位置时经常会出现这种情况。 例如,所有 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

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

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

因为您希望乘以的数字和插入的桶数具有正交素数分解。

假设有 8 个桶要插入。 如果您用来乘以的数字是 8 的某个倍数,那么插入的存储桶将仅由最不重要的条目(根本没有乘以的条目)确定。 类似的条目会发生冲突。 不适合散列函数。

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

值得一提的是, Effective Java 2nd Edition放弃了数学问题,只是说选择 31 的原因是:

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

这是第 9 项中的完整引用:当您覆盖equals时,始终覆盖hashCode

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

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

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

现代虚拟机会自动进行这种优化。


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

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

更简单地说,可以说使用具有多个除数的乘数会导致更多的哈希冲突 由于为了有效散列,我们希望最小化冲突的数量,我们尝试使用具有较少除数的乘法器。 根据定义,素数正好有两个不同的正除数。

相关问题

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

这是一个更接近来源的引文

归结为:

  • 31 是素数,减少冲突
  • 31 产生良好的分布,具有
  • 速度的合理权衡

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

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

如果散列表和乘数有一个公因子n的坏影响可能是在某些情况下散列表中只有 1/n 个条目会被使用。

使用质数的原因是在数据表现出某些特定模式时尽量减少冲突。

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

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

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

10 模 4 = 2

20 模 4 = 0

30 模 4 = 2

40 模 4 = 0

50 模 4 = 2

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

如果我们使用像 7 这样的质数:

10 模 7 = 3

20 模 7 = 6

30 模 7 = 2

40 模 7 = 4

50 模 7 = 1

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

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

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

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM