HashMap的resize方法源码流程简析

HashMap作为一种集合,其容量自然不可能是无限的,既然容量有限,当数据存储到一定量的时候就会进行扩容,因此本篇文章对HashMap的扩容做一下源码简析,文章目录如下
在这里插入图片描述

一、源码简析

final Node<K,V>[] resize() {
        Node<K,V>[] oldTab = table;
        // 获取扩容前哈希桶的长度
        int oldCap = (oldTab == null) ? 0 : oldTab.length;
        int oldThr = threshold;
        int newCap, newThr = 0;
        // 判断扩容前哈希桶长度是否大于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) {
        	// 老哈希桶从索引0开始向后遍历
            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
                    	// lo链表挂在当前索引位置
                        Node<K,V> loHead = null, loTail = null;
                        // hi链表挂在当前索引+原哈希桶长度的位置
                        Node<K,V> hiHead = null, hiTail = null;
                        Node<K,V> next;
                        do {
                            next = e.next;
                            // 用hash值和老哈希桶长度进行与运算来判断将节点放置在哪个链表上
                            if ((e.hash & oldCap) == 0) {
                                if (loTail == null)
                                	// 如果lo链表尾节点为空,则将改节点设置为头节点
                                    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 (loTail != null) {
                            loTail.next = null;
                            // 将lo链表挂在当前索引位置
                            newTab[j] = loHead;
                        }
                        if (hiTail != null) {
                            hiTail.next = null;
                            // 将hi链表挂在当前索引+原哈希桶长度的位置
                            newTab[j + oldCap] = hiHead;
                        }
                    }
                }
            }
        }
        return newTab;
    }

扩容的过程大体可以分为两部分,以代码中的分隔线为界限,前半部分主要是计算扩容后新哈希桶的长度,后半部分主要是对老哈希桶数据的迁移

二、计算扩容长度

这部分没有要太多说明的地位,主要是计算规则的采用的二倍扩容,之所以采取二倍,是因为哈希桶的长度要求是2的幂次方,如果是四倍或者更大的话,则容易造成空间的浪费,所以二倍扩容是比较合适的

三、数据迁移

在这里插入图片描述
1、移动的位置:
源码上是e.hash & (newCap - 1),而在put方法上是(n - 1) & hash,两者都是用hash值与现有哈希桶长度-1来进行与运算,假设初始长度是16,则n-1为15,二进制为11111,而扩容后长度为32,则newCap - 1为31,二进制为111111,后者的计算结果差值为100000,转为十进制则为16,也就是原哈希桶的长度,因为移动后的位置要么就是原索引位置,要么就是原索引位置+原哈希桶长度,这应该也算是hash寻址的特点之一吧
2、链表的转移
因为上述的移动位置的特性,JDK1.8对于链表做了算法优化,它先将链表拆分成lo链表和hi链表,两个链表,然后将两个链表挂别挂在上述指定的两个位置上,省去了对链表一个个寻址挂载的步骤,提高了效率,这里还需要注意是,两个链表的数量并不是平均的,也没有固定的顺序逻辑

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值