许多书籍和教程都说哈希表的大小必须是一个素数,才能在所有桶中均匀分布密钥。但是 JavaHashMap
总是使用 2 的幂的大小。不应该使用素数吗?哪个更好,“素数”或“2 的幂”作为哈希表大小?
5 回答
使用 2 的幂有效地掩盖了哈希码的最高位。因此,质量差的散列函数在这种情况下可能表现得特别差。
Java通过不信任对象的实现并对其结果应用第二级散列来HashMap
缓解这种情况:hashCode()
对给定的 hashCode 应用补充散列函数,以防止质量差的散列函数。这很关键,因为 HashMap 使用长度为二的幂的哈希表,否则会遇到低位没有差异的 hashCode 的冲突。
如果你有一个好的散列函数,或者做一些类似的事情HashMap
,你是否使用素数等作为表大小都没有关系。
另一方面,如果散列函数未知或质量差,那么使用质数将是更安全的选择。但是,它会使动态大小的表格难以实现,因为突然之间,您需要能够生成素数,而不是仅仅将大小乘以一个常数因子。
标准的 HashMap 实现有一个hash
方法可以重新散列对象的哈希码以避免这种陷阱。方法前的hash()
注释如下:
/**
* 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.
*/
了解素数和二次幂之间哪个更好的唯一方法是对其进行基准测试。
许多年前,在编写一个性能强烈依赖于符号表查找的汇编程序时,我使用大量生成的标识符对其进行了测试。即使使用简单的映射,我发现与预期的一样,与类似大小的素数桶相比,二次幂的分布更不均匀,链更长。由于位掩码的桶选择速度,它仍然运行得更快。
我强烈怀疑 java.util 开发人员不会使用额外的散列和二次幂,而不是针对使用质数桶进行基准测试。在设计散列数据结构时,这是一件非常明显的事情。
出于这个原因,我确信 rehash 和二次幂大小为典型的 Java 哈希映射提供了比质数桶更好的性能。
从性能/计算时间的角度来看,可以仅使用位掩码来计算二次方大小,这比否则需要的整数除法更快。
如果您使用二次探测来解决冲突,您可能应该使用素数大小的哈希表。如果您有一个素数大小的表,二次探测将命中一半的条目,如果不是素数则更少。因此,即使您的哈希表未满一半,您也可能找不到合适的位置来存储您的条目。由于 Java 哈希映射不使用二次探测,因此不需要使用素数作为大小。