JAVA Hashmap内部实现 - 如果bin变得太大会怎样

时间:2015-07-09 18:23:22

标签: java data-structures hashmap

在内部,Hashmaps使用hashfunction查找查询bin所属的key。这些bins中的每一个本身都是LinkedList

我不明白如果这些LinkedLists可能会变得很长,并且LinkedLists没有持续的访问时间,而是线性访问时间,那么访问时间是如何保持不变的。

即使由于某些原因垃圾箱太大,Java Collections库如何设法保证持续的访问时间?内部发生了什么? Java在内部做了什么来最大限度地减少这种负面影响?

4 个答案:

答案 0 :(得分:5)

每个bin中的平均元素数量由一个小常量绑定。通过保持箱的数量至少与条目总数乘以载荷因子(其默认值为0.75)一样高来维持这一点。

为了保持这种不变性,容器的数量会随着条目的数量而增加。

以下是相关代码(Java 7):

void addEntry(int hash, K key, V value, int bucketIndex) {
    Entry<K,V> e = table[bucketIndex];
    table[bucketIndex] = new Entry<>(hash, key, value, e);
    if (size++ >= threshold)
        resize(2 * table.length);
}

size是条目数,table.length是区间数,thresholdtable.length * loadFactor

如果您使用默认加载因子0.75(或任何加载因子&lt; 1),则bin的数量将始终高于条目数,因此除非您的密钥类具有非常糟糕的hashCode, bin平均不会有多个条目。

答案 1 :(得分:2)

documentation tells you,如果负载系数过高,会发生什么:

  

HashMap的一个实例有两个影响其性能的参数:初始容量和负载因子。容量是哈希表中的桶数,初始容量只是创建哈希表时的容量。加载因子是在自动增加容量之前允许哈希表获取的完整程度的度量。 当哈希表中的条目数超过加载因子和当前容量的乘积时,哈希表被重新哈希(即重建内部数据结构),以便哈希表具有近似值桶数量的两倍。

此外,您还可以查看源代码,其中列出了implementation notes的完整列表。最重要的是:

  

此映射通常用作分区(分区)散列表,但是当分区变得太大时,它们会转换为TreeNodes的分区,每个分区的结构与java.util.TreeMap中的类似。

并进一步:

  

由于TreeNodes的大小约为常规节点的两倍,因此我们仅在bin包含足够的节点以保证使用时使用它们(请参阅TREEIFY_THRESHOLD)。当它们变得太小(由于移除或调整大小)时,它们会转换回普通垃圾箱。

简而言之:

  • 当单个容器变得太大时,它们的元素会转换为树节点,并且O(ln(t))搜索bin的大小为t。所以大型箱子有一个悬挂它们的二叉树。
  • 当整个地图的载荷系数变高时,箱子的数量加倍并且整个地图被重新定位(这可能仍会导致一些箱子再次成为树箱)。

答案 2 :(得分:2)

  

我不明白如果这些链接列表可能会变得非常长,访问时间是如何不变的

HashMap不提供有保证的常量访问时间。它提供了分摊的常量时间,这是另一回事:n项的总体访问平均为O(1),但每个访问可能是O(n)。< / p>

此外,仅当散列函数为“好”时才实现摊销的常数时间。当散列函数很糟糕时(例如,返回一个常量,这是一个有效但非常糟糕的散列函数),库很无奈:无论实现的是什么,访问时间都是线性的。

当多个哈希码相同时,链表将增长,以桶的数量为模。但是,由于HashMap为其桶数选择素数,因此链表变得非常长的最常见情况是许多哈希码实际上是相同的,而不考虑模数。因此,简单地将桶的数量增加到更大的素数不会减少列表的长度:它会将列表移动到不同的桶,或者将其保留在旧的位置,但列表的长度不会减少。

答案 3 :(得分:1)

如果哈希表太满,则需要重新哈希。要重新散列表,将创建另一个包含更多存储桶的表,并将所有元素插入到新表中。原始表格被丢弃。

负载系数决定何时重新散列。默认值为0.75,因此当表格大于75%时,它会自动重新加载两倍的桶。

要在表中查找位置,计算哈希码并以桶的数量为模。这个想法是散列函数应该在某种程度上随机分布对象,因此碰撞的数量应该很少,因此不应该进行太多的比较。