比较JDK 1.6中的HashMap
和Hashtable
源代码,我在HashMap中看到了以下代码:
/**
* The default initial capacity - MUST be a power of two.
*/
static final int DEFAULT_INITIAL_CAPACITY = 16;
int capacity = 1;
while (capacity < initialCapacity)
capacity <<= 1;
然而,在Hashtable中,我看到了这一点:
table = new Entry[initialCapacity];
public Hashtable() {
this(11, 0.75f);
}
所以我的问题是: 为什么HashMap需要2的幂作为初始容量,而Hashtable选择11作为默认初始容量? 我认为这与Hashtable是线程安全的并且不允许空键或值的事情无关。
答案 0 :(得分:22)
以下文章详细介绍了这个问题:HashMap requires a better hashCode() - JDK 1.4 Part II。
根据那篇文章,转向两种尺寸的主要原因是位掩码比整数除法更快。这并非没有不良后果,原作者之一解释了这一点:
Joshua Bloch:使用二次幂的缺点是生成的哈希表对哈希函数(hashCode)的质量非常敏感。输入中的任何更改都必须影响哈希值的低位比特。 (理想情况下,它应该以相同的可能性影响散列值的所有位。)因为我们无法保证这是真的,所以当我们切换到2的幂时,我们放入次要(或“防御性”)散列函数哈希表。在屏蔽低阶位之前,将该散列函数应用于hashCode的结果。它的工作是将信息分散在所有位上,特别是分散到低位。当然它必须快速运行非常,否则你就失去了切换到两倍大小的表的好处。 1.4中的原始二级散列函数证明是不够的。我们知道这是理论上的可能性,但我们认为它不会影响任何实际数据集。我们错了。替换的二级哈希函数(我在计算机的帮助下开发)具有强大的统计特性,几乎可以保证良好的桶分布。
答案 1 :(得分:6)
Hashtable使用伪素数表大小并且增大表的大小相对较慢。 HashMap使用2的幂,比使用模数更快。
具有讽刺意味的是,2的幂的模数意味着需要一个好的hashCode(),因为顶部的位将被忽略,因此HashMap有一个重新排列hashCode的方法,以避免这个问题,这意味着它实际上可能更慢。位:Z答案 2 :(得分:3)
这可能有所帮助:
http://www.concentric.net/~Ttwang/tech/primehash.htm
基本上,如果我没记错的话,当你的哈希表的大小为2的幂时,很容易根据密钥的相关位较小来获得哈希函数。
使用素数(如11)作为表的大小,使得表行的冲突不太可能,因此插入“更便宜”。
答案 3 :(得分:0)
表大小为2的幂的要求是一个实现细节,这个类的用户不知道 - 这就是为什么c'tor默默地将值调整为下一个更大的2的幂而不是标记错误。
Hashtable实现假设散列可能不是均匀分布的,因此它试图使用一些主要的bin,以避免散列的频率分布中的峰值。
这两个实现细节的组合会导致性能不佳。
(例如,原始哈希函数将是
int hash(String s, int nBins) {
return s[0] % nBins;
}
如果nBins为32,则e
和E
最终位于同一个bin中,因此散列值的分布与字母出现的分布相关,这些字母具有明显的峰值 - 所以频率分配将在32处达到峰值。)