HashMap的扩容机制

Java HashMap 的扩容机制是一个核心功能,用以保证 HashMap 性能随着元素的增加而适度下降。这里将详细介绍其扩容机制。

扩容机制的工作流程:

  1. 触发条件:
    当 HashMap 中的元素数量超过 threshold(阈值)时,触发扩容。这个阈值是当前容量(capacity)与负载因子(loadFactor,默认值为0.75)的乘积。

  2. 新容量计算:
    新的容量是当前容量的两倍,HashMap 容量总是保持 2 的幂次。

  3. 创建新数组:
    新的 Node 数组(桶)被创建,大小为新的容量。

  4. 重新哈希:
    现有的键值对必须被移到新的桶中。这需要对每个键重新计算其在新数组中的位置。

  5. 重新分配元素:
    对于旧数组中的每个桶,遍历桶中的每个节点(可能是链表或红黑树),并将它们移动到新的桶中。在这个过程中,由于数组大小翻倍,某些元素的位置会发生变化。

resize 方法源码解析:

// HashMap的resize方法源码片段(简化版,基于Java 8)
final Node<K,V>[] resize() {
    Node<K,V>[] oldTab = table;
    int oldCap = (oldTab == null) ? 0 : oldTab.length;
    int oldThr = threshold;
    int newCap, newThr = 0;

    if (oldCap > 0) {
        if (oldCap >= MAXIMUM_CAPACITY) {
            threshold = Integer.MAX_VALUE;
            return oldTab;
        }
        else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
                 oldCap >= DEFAULT_INITIAL_CAPACITY)
            newThr = oldThr << 1; // double threshold
    }
    else if (oldThr > 0) // initial capacity was placed in threshold
        newCap = oldThr;
    else {               // zero initial threshold signifies using defaults
        newCap = DEFAULT_INITIAL_CAPACITY;
        newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
    }
    if (newThr == 0) {
        float ft = (float)newCap * loadFactor;
        newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
                  (int)ft : Integer.MAX_VALUE);
    }

    threshold = newThr;
    @SuppressWarnings({"rawtypes","unchecked"})
    Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
    table = newTab;
    if (oldTab != null) {
        for (int j = 0; j < oldCap; ++j) {
            Node<K,V> e;
            if ((e = oldTab[j]) != null) {
                oldTab[j] = null;
                if (e.next == null)
                    newTab[e.hash & (newCap - 1)] = e;
                else if (e instanceof TreeNode)
                    ((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
                else { // preserve order
                    Node<K,V> loHead = null, loTail = null;
                    Node<K,V> hiHead = null, hiTail = null;
                    Node<K,V> next;
                    do {
                        next = e.next;
                        if ((e.hash & oldCap) == 0) {
                            if (loTail == null)
                                loHead = e;
                            else
                                loTail.next = e;
                            loTail = e;
                        }
                        else {
                            if (hiTail == null)
                                hiHead = e;
                            else
                                hiTail.next = e;
                            hiTail = e;
                        }
                    } while ((e = next) != null);
                    if (loHead != null) {
                        loTail.next = null;
                        newTab[j] = loHead;
                    }
                    if (hiHead != null) {
                        hiTail.next = null;
                        newTab[j + oldCap] = hiHead;
                    }
                }
            }
        }
    }
    return newTab;
}

resize 方法中,首先计算新的容量和阈值。然后,创建一个新的 Node 数组作为新的桶。对于旧数组中的每个非空桶,将遍历其中的节点,并根据其哈希值在新的桶中重新分配位置。如果节点是一个红黑树节点,会调用 split 方法来处理。如果节点是链表,会维护两个链表头,一个链表头用来处理位置不变的元素,另一个链表头用来处理位置发生变化的元素(即原索引位置加上旧容量的位置)。

代码演示:

// 创建一个小容量的HashMap来演示扩容
HashMap<Integer, String> map = new HashMap<>(2);
map.put(1, "a"); // 添加元素,触发扩容
map.put(2, "b"); // 继续添加元素
map.put(3, "c"); // 再次触发扩容

// 打印出HashMap的状态来观察元素的分布
for (Map.Entry<Integer, String> entry : map.entrySet()) {
    System.out.println("Key: " + entry.getKey() + " - Value: " + entry.getValue());
}

// 打印出实际的桶数量(capacity)
System.out.println("HashMap capacity: " + getField(map, "table").length);

这段代码演示了一个 HashMap 的创建和扩容过程。创建时指定了初始容量为 2,随着元素的添加,它会自动扩容以保持预定的负载因子。

请注意,getField(map, "table").length 这个调用用来检查内部数组的大小,需要反射来实现,这里只是为了演示,实际中并不推荐这样做,因为内部实现细节可能会变化,并且反射会绕过访问控制而破坏封装性。

HashMap 的扩容是一个成本较高的操作,因为它涉及到重新计算元素的位置并重新分配它们。这也是为什么在创建 HashMap 时提供初始容量和负载因子是一个好的实践,尤其是当你预期会有大量元素存储在 HashMap 中时。这样可以减少扩容的次数,从而提高整体性能。

  • 14
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

辞暮尔尔-烟火年年

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值