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 大小的“素数”数字还是“二的幂”?的主要内容,如果未能解决你的问题,请参考以下文章