HashMap resize()方法逐行代码解析说明

final Node<K,V>[] resize()方法的作用是进行哈希表容量的初始化或扩容,在向HashMap插入数据的时候会被调用到,具体实际的操作可能有如下几种情况:

  • 直接复制(只有一个节点的桶)
  • 链表拆分(有多个节点的桶)
  • 链表转红黑树 或 红黑树恢复链表

实际代码及详细注释如下:

    /**
     * Initializes or doubles table size.  If null, allocates in
     * accord with initial capacity target held in field threshold.
     * Otherwise, because we are using power-of-two expansion, the
     * elements from each bin must either stay at same index, or move
     * with a power of two offset in the new table.
     *
     * @return the table
     */
    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) { // 如果旧容量大于0,正常使用情况下
            // 如果旧容量大于最大容量值,新容量等于Integer最大值,阈值不变,返回旧哈希表。
            if (oldCap >= MAXIMUM_CAPACITY) {
                threshold = Integer.MAX_VALUE;
                return oldTab;
            }
            // 如果旧容量没超过最大容量值,新容量=旧容量×2(左移1位相当于×2),
            // 然后判断新容量是否小于最大容量并且旧容量大于初始容量
            // 如果都符合,新阈值=旧阈值×2(左移1位相当于×2)
            else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
                     oldCap >= DEFAULT_INITIAL_CAPACITY)
                newThr = oldThr << 1; // double threshold
        }
        // 如果旧阈值大于0,新的容量等于旧的阈值,
        // 也就是:指定阈值创建对象但是却没用过此HashMap的时候
        else if (oldThr > 0) // initial capacity was placed in threshold
            newCap = oldThr;
        // 否则(说明旧容量和旧阈值都不大于0,也就是HashMap没有初始化过)
        // 那么就是执行的初始化操作,容量和阈值均采用默认值。
        else {               // zero initial threshold signifies using defaults
            newCap = DEFAULT_INITIAL_CAPACITY;
            newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
        }
        if (newThr == 0) { // 新阈值为0,也就是和上面的else if 情况一样的时候
            // 如果新的容量和新容量×负载因子 均小于最大容量 新阈值=新容量×负载因子
            // 否则新阈值为Integer最大值。
            // 这里 新容量×负载因子大于最大容量判断在后,所以应该是为了避免自定义负载因子大于1的情况
            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) { // 如果桶不为空,e为该桶的头结点
                    oldTab[j] = null;
                    if (e.next == null) // 如果桶只有一个节点,直接赋值给新的HashMap的桶
                        newTab[e.hash & (newCap - 1)] = e;
                    else if (e instanceof TreeNode) // 链表转红黑树 或 红黑树恢复链表
                        ((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
                    else { // preserve order // 如果是链表,对链表进行拆分, lo和hi分别代表新旧链表,扩容后重新按照哈希值找Node应放的桶
                        Node<K,V> loHead = null, loTail = null; // 链表拆分会放到lo和hi两个链表中,最终lo放到原索引的桶的位置
                        Node<K,V> hiHead = null, hiTail = null; // 链表拆分会放到lo和hi两个链表中,最终hi放到扩容出来的新索引的桶的对应位置
                        Node<K,V> next; // 临时变量存储下一个节点
                        do {
                            next = e.next; // 给next赋值下一个节点
                            // 因为扩容是<<1,相当于×2,所以会把一个桶中的节点放到两个桶中
                            // 正常获取桶位置是与上cap-1,此时与上cap就会将一种结果变成两种结果
                            // 就会把同一个桶中的节点分为两批,分别放入两个桶中,
                            // 不理解可以自己算一个,比如cap=4,有几个key哈希值是1、5、9、13,
                            // 和3(cap-1)与一下,再和4(cap)与一下,看结果就懂了。
                            if ((e.hash & oldCap) == 0) { // 尾插法插入到lo链表
                                if (loTail == null)
                                    loHead = e;
                                else
                                    loTail.next = e;
                                loTail = e;
                            }
                            else { // 尾插法插入到hi链表
                                if (hiTail == null)
                                    hiHead = e;
                                else
                                    hiTail.next = e;
                                hiTail = e;
                            }
                        } while ((e = next) != null);
                        if (loTail != null) { // 将lo链表放到原来桶对应索引位置
                            loTail.next = null;
                            newTab[j] = loHead;
                        }
                        if (hiTail != null) { // 将hi链表放到新扩容出来的桶的对应位置
                            hiTail.next = null;
                            newTab[j + oldCap] = hiHead;
                        }
                    }
                }
            }
        }
        return newTab; // 返回新哈希表
    }
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值