在线检查java和google搜索哈希表代码示例,似乎通过加倍来完成表的大小调整。
但是大多数教科书都说桌子的最佳尺寸是素数。
所以我的问题是:
加倍的方法是因为:
- 它易于实现,或
- 找到素数太低效(但我认为这个发现
下一个素数会结束
n+=2
并使用。来测试素数
modulo是O(loglogN)哪个便宜)
- 或者这是我的误解,只有某些哈希表变体
只需要素数表?
更新:
某些属性需要使用素数在教科书中呈现的方式(例如,二次探测需要一个素数大小表来证明例如如果一个表不是完整的项目X将被插入)。
发布为重复的链接通常要求增加任何数量,例如25%或下一个素数和答案接受表明我们加倍,以保持调整大小操作“罕见”,所以我们可以保证摊销时间。
这并没有回答这样一个问题:使用一个表格大小是素数并且使用素数来调整大小甚至大于两倍。因此,我们的想法是保持主要大小的属性考虑调整大小开销
问: 但是大多数教科书都说桌子的最佳尺寸是素数。
关于大小素数:
什么是大小的素数,它取决于你选择的碰撞分辨率算法。一些算法需要素数表大小(双重散列,二次散列),其他算法不需要,并且它们可以从2的幂表大小中受益,因为它允许非常便宜的模运算。但是,当最接近的“可用表大小”相差2倍时,哈希表的内存使用可能不可靠。因此,即使使用线性散列或单独链接,您也可以选择2大小的非幂。在这种情况下,反过来,值得选择特定的素数,因为:
如果你选择素数表大小(因为算法需要这个,或者因为你对2的幂大小暗示的内存使用不可靠性不满意),表格槽计算(按表大小模数)可以与散列相结合。看到 这个答案 更多。
当哈希函数分布不好(来自Neil Coffey的答案)时,表2的幂大小是不可取的,这是不切实际的,因为即使你有糟糕的哈希函数,雪崩它仍然使用2的幂大小将是切换到主表大小的速度更快,因为在现代CPU上单个积分除法仍然较慢,因为良好的雪崩函数所需的多个多重复用和移位操作,例如。 G。来自MurmurHash3。
问: 另外说实话,如果你真的推荐素数,我会迷失一点。似乎它取决于哈希表变体和哈希函数的质量?
哈希函数的质量无关紧要,你总是可以通过MurMur3平衡来“改进”哈希函数,这比从2表格大小切换到素数表大小便宜,见上文。
我建议选择素数大小,使用QHash或二次散列算法(不一样) 只要 什么时候需要 精确控制哈希表加载因子 和 可预测的高实际负载。对于2的幂表大小,最小调整大小因子是2,并且通常我们不能保证散列表将具有高于0.5的实际负载因子。 看到这个答案。
否则,我建议使用带有线性探测功能的2-power大小哈希表。
问: 加倍的方法是因为:
它易于实现,或
基本上,在许多情况下,是的。
看到 关于载荷因子的这个大答案:
负载因子不是哈希表数据结构的重要部分 - 它是为动态系统定义行为规则的方法(增长/收缩哈希表是一个动态系统)。
此外,在我看来,在95%的现代哈希表案例中,这种方式过于简化,动态系统表现不理想。
什么是 加倍?这只是最简单的调整大小策略。该策略可能是任意复杂的,在您的用例中表现最佳。它可以考虑当前的哈希表大小,增长强度(自上次调整大小以来完成了多少操作)等等。没有人禁止你实现这样的自定义大小调整逻辑。
问: 找到素数太低效了(但我认为找到下一个素数超过n + = 2并使用模数测试素数是O(loglogN)这很便宜)
有一个很好的做法是预先计算一些主要哈希表大小的子集,在运行时使用二进制搜索在它们之间进行选择。看到 列表双哈希容量和解释, QHash能力。或者,甚至使用 直接查找,这是非常快的。
问: 或者这是我的误解,只有某些散列表变体只需要主表大小?
是的,只有某些类型需要,见上文。
Java HashMap(java.util.HashMap
链链接列表中的桶冲突(或[从JDK8]树开始,取决于块的大小和过量填充)。
因此,关于二次探测功能的理论不适用。
似乎消息“使用散列表的素数大小”已经脱离了多年来适用的情况......
使用2的幂具有将表格条目的哈希值减少的优点(如其他答案所述)可以通过比特掩码来实现。整数除法相对昂贵,在高性能情况下,这可能有所帮助。
我将观察到“重新分配碰撞链时,重新划分对于能够为2的幂的表来说是一个很大的力量”。
请注意,当使用两次重新散列的权限时,根据哈希码的“下一个”位,在两个桶之间“拆分”每个桶。
也就是说,如果哈希表有256个桶,那么使用哈希值重新散列的最低8位根据第9位拆分每个冲突链,并且保留在同一个桶B(第9位为0)或转到桶B + 256(第9位为1)。这种分裂可以保留/利用铲斗处理方法。例如, java.util.HashMap
保持小桶按照插入的相反顺序排序,然后将它们分成遵循该顺序的两个子结构。它将大桶保存在按哈希码排序的二叉树中,并类似地拆分树以保留该顺序。
注意: 直到JDK8才实现这些技巧。
(我很确定) Java.util.HashMap
只有大小(从不下降)。但是将哈希表减半使其加倍也有类似的效率。
这种策略的一个“缺点”是 Object
没有明确要求实现者确保散列码的低阶位分布良好。
完全有效的哈希码可以在整体上很好地分布,但在其低阶位中分布不佳。因此,服从一般合同的对象 hashCode()
实际使用时可能仍然是坦克 HashMap
!
Java.util.HashMap
通过在提供的上应用额外的散列“spread”来缓解这个问题 hashCode()
实现。那''传播'真的是快速的原油(16位高位和低位)。
对象implmenters应该知道(如果还没有)他们的哈希码(或缺少哈希码)中的偏差会对使用哈希的数据结构的性能产生重大影响。
为了记录,我将此分析基于此源的副本:
http://hg.openjdk.java.net/jdk8/jdk8/jdk/file/687fd7c7986d/src/share/classes/java/util/HashMap.java