Java:作为 HashMap 大小的“素数”数字还是“二的幂”?

Posted

技术标签:

【中文标题】Java:作为 HashMap 大小的“素数”数字还是“二的幂”?【英文标题】:Java: A "prime" number or a "power of two" as HashMap size? 【发布时间】:2013-03-04 11:01:07 【问题描述】:

许多书籍和教程都说哈希表的大小必须是素数,才能在所有桶中均匀分布密钥。但是Java 的HashMap 总是使用2 的幂的大小。不应该使用素数吗?哈希表大小是“质数”还是“2 的幂”哪个更好?

【问题讨论】:

我怀疑他们是否真的这么说,如果他们这样做了,那就错了。这只是一种方法。 【参考方案1】:

使用 2 的幂可以有效地屏蔽哈希码的最高位。因此,在这种情况下,质量差的哈希函数可能会表现得特别差。

Java 的 HashMap 通过不信任对象的 hashCode() 实现和 applying a second level of hashing to its result 来缓解这种情况:

对给定的 hashCode 应用补充散列函数,以防止质量差的散列函数。这一点很关键,因为 HashMap 使用长度为二的幂的哈希表,否则会遇到低位没有差异的 hashCode 的冲突。

如果你有一个好的散列函数,或者做一些类似于HashMap 所做的事情,那么你是否使用素数等作为表大小都没有关系。

另一方面,如果散列函数未知或质量差,那么使用质数将是更安全的选择。但是,它会使动态大小的表格难以实现,因为突然之间您需要能够生成素数,而不是仅仅将大小乘以一个常数因子。

【讨论】:

出于好奇:为什么? (或者你有解释这个的参考/链接)? 你确定桌子的大小无关紧要吗?一个好的散列函数的意义不在于将数据分散到整个表中,以减少冲突的数量吗?但是如果表非常小,那么无论散列函数如何,冲突都会增加。我错过了什么吗? @pamphlet:很明显,越大越好(或者至少不太可能变得更糟)。然而,对于类似的大小,没有理由更喜欢素数等,只要哈希函数是高质量的 @pamphlet:我已经改写了答案的那一部分,因为我的意思不是很清楚。 @pamphlet 当超过负载因子时,表的容量会增加(大约两倍),所以即使表的初始容量很小,表也会扩展到适合包含的最大条目数的大小。一个好的初始猜测意味着随着表格的增长而减少调整大小,但一个糟糕的初始猜测最终仍会产生一个有效的表格。【参考方案2】:

标准的 HashMap 实现有一个 hash 方法,它可以重新散列对象的哈希码以避免这种陷阱。 the hash() method 之前的评论内容为:

/**
 * Retrieve object hash code and applies a supplemental hash function to the
 * result hash, which defends against poor quality hash functions.  This is
 * critical because HashMap uses power-of-two length hash tables, that
 * otherwise encounter collisions for hashCodes that do not differ
 * in lower bits. Note: Null keys always map to hash 0, thus index 0.
 */

【讨论】:

【参考方案3】:

了解素数和二次幂之间哪个更好的唯一方法是对其进行基准测试。

许多年前,在编写一个性能强烈依赖于符号表查找的汇编程序时,我使用大量生成的标识符对其进行了测试。即使使用简单的映射,我发现与预期的一样,与类似大小的素数桶相比,二次幂的分布更不均匀,链更长。由于位掩码的桶选择速度,它仍然运行得更快。

我强烈怀疑 java.util 开发人员不会使用额外的散列和二的幂,而不是针对使用质数桶进行基准测试。在设计散列数据结构时,这是一件非常明显的事情。

出于这个原因,我确信 rehash 和二次幂大小为典型的 Java 哈希映射提供了比质数桶更好的性能。

【讨论】:

.NET 的 HashMap 实现(他们称之为字典)使用硬编码的素数列表来表示存储桶大​​小。所以,我不太确定 rehash 与 primes 的优势,至少我不会将这个结论推广到其他平台。【参考方案4】:

从性能/计算时间的角度来看,可以仅使用位掩码来计算二次方大小,这比整数除法要快,否则需要整数除法。

【讨论】:

【参考方案5】:

如果您使用quadratic probing 解决冲突,您可能应该使用素数大小的哈希表。如果您有一个素数大小的表,二次探测将命中一半的条目,如果不是素数则更少。因此,即使您的哈希表未满一半,您也可能找不到合适的位置来存储您的条目。由于 Java 哈希映射不使用二次探测,因此无需使用素数作为大小。

【讨论】:

以上是关于Java:作为 HashMap 大小的“素数”数字还是“二的幂”?的主要内容,如果未能解决你的问题,请参考以下文章

Java HashMap类源码解析

HashMap?

Java中带指数的素数分解

为啥 HashMap 的初始容量是 16(2 的幂),而 Hashtable 的初始容量是 11(素数)?

为啥哈希表的大小为 127(素数)优于 128?

找出所有小于 200 万的素数之和需要多少时间?