JDK1.8---HashMap的resize()扩容方法源码详解

resize()方法触发时机

首先总结一下JDK1.8的HashMap都在什么时候触发resize()方法,根据阅读源码总结了三个时机触发扩容,这里只做介绍,后面根据源码详细分析

HashMap是由数组+链表+红黑树构成的,数组就称之为桶了

①众所周知当HashMap的使用的桶数达到总桶数*加载因子的时候会触发扩容;
②当某个桶中的链表长度达到8进行链表扭转为红黑树的时候,会检查总桶数是否小于64,如果总桶数小于64也会进行扩容;
③当new完HashMap之后,第一次往HashMap进行put操作的时候,首先会进行扩容。原理会在下面根据源码分析,这里先做个例子并思考一下原因,下面分析完自然懂得原因。
在这里插入图片描述
在这里插入图片描述
首先思考一个问题:为何new出来HashMap的时候并没有报OOM,而是在第一次进行put操作的时候才报的OOM?

为了更好的分析扩容首先我们先看一下HashMap带参数的构造函数
在这里插入图片描述
这里的this.threshold本应该指的是HashMap的下次扩容的阈值,仔细看你会发现这里并没有对组成HashMap的数组按你写的大小进行初始化,而是把你的参数赋值给下次的扩容的阈值。这也就解决了上一个问题出现的原因,当第一次put值的时候会对这个数组是否初始化进行检测,如果没有初始化会先触发resize方法,那么什么时候才真正对其初始化的呢,请看下面分析

再看一下tableSizeFor函数
在这里插入图片描述
这个函数是用来对你申请的容量进行处理让他变成最接近你申请的容量的2次幂的大小,这里注意:假如你申请的容量为0,最后处理的结果会变成1,代表着你最小的容量为1

下面开始根据源码介绍HashMap的扩容原理

首先看一下resize()源码,可以先不用读,看我下面分析再返回源码看我提的重要的位置

1 final Node<K,V>[] resize() {
2        Node<K,V>[] oldTab = table;
3        int oldCap = (oldTab == null) ? 0 : oldTab.length;
4        int oldThr = threshold;
5        int newCap, newThr = 0;
6        if (oldCap > 0) {
7            if (oldCap >= MAXIMUM_CAPACITY) {
8                threshold = Integer.MAX_VALUE;
9                return oldTab;
10            }
11            else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
12                     oldCap >= DEFAULT_INITIAL_CAPACITY)
13
14                newThr = oldThr << 1; // double threshold
15        }
16        else if (oldThr > 0) // initial capacity was placed in threshold
17            newCap = oldThr;
18        else {               // zero initial threshold signifies using defaults
19            newCap = DEFAULT_INITIAL_CAPACITY;
20            newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
21        }
22        if (newThr == 0) {
23            float ft = (float)newCap * loadFactor;
24            newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
25                      (int)ft : Integer.MAX_VALUE);
26        }
27        threshold = newThr;
28        @SuppressWarnings({"rawtypes","unchecked"})
29            Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
30        table = newTab;
31        if (oldTab != null) {
32            for (int j = 0; j < oldCap; ++j) {
33                Node<K,V> e;
34                if ((e = oldTab[j]) != null) {
35                    oldTab[j] = null;
36                    if (e.next == null)
37                        newTab[e.hash & (newCap - 1)] = e;
38                    else if (e instanceof TreeNode)
39                        ((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
40                    else { // preserve order
41                        Node<K,V> loHead = null, loTail = null;
42                        Node<K,V> hiHead = null, hiTail = null;
43                        Node<K,V> next;
44                        do {
45                            next = e.next;
46                            if ((e.hash & oldCap) == 0) {
47                                if (loTail == null)
48                                    loHead = e;
49                                else
50                                    loTail.next = e;
51                                loTail = e;
52                            }
53                            else {
54                                if (hiTail == null)
55                                    hiHead = e;
56                                else
57                                    hiTail.next = e;
58                                hiTail = e;
59                            }
60                        } while ((e = next) != null);
61                        if (loTail != null) {
62                            loTail.next = null;
63                            newTab[j] = loHead;
64                        }
65                        if (hiTail != null) {
66                            hiTail.next = null;
67                            newTab[j + oldCap] = hiHead;
68                        }
69                    }
70                }
71            }
72        }
73        return newTab;
74    }

首先介绍一下其中的重要的局部变量吧,容易更清楚下面分析的含义
oldTab:为数组类型,代表扩容之前HashMap中的数组,也就是所有的桶;
oldCap:为int类型代表扩容之前总桶数量;
oldThr:为int类型代表扩容之前下次扩容的阈值;
newCap:为int类型代表这次扩容之后总桶数量;
newThr:为int类型代表这次扩容之后下次扩容的阈值;
newTab:为数组类型,代表扩容之后HashMap中的数组。

下面进行分步分析扩容

[1] 首先在第3行对扩容之前HashMap的数组是否为空进行判断,上面已经分析过了当第一次new出来的HashMap之后数组就是空的,oldTab会被赋值为0,如果之前不是空的,就是该数组的长度;

[2] 在第6行判断oldCap是否大于0,如果大于0说明之前HashMap的数组不是空的,再对oldCap进行检测是否达到最大容量,若果达到最大容量就把下次扩容的阈值设置为int类型的最大值并且返回;否则就进行扩容为之前的二倍基于右移;

[3] 当oldCap为0,并且oldThr大于0的时候,以为着这是第一次进行扩容,在16、17行把newCap赋值为扩容之前的阈值,该阈值当时代表着是你要申请的容量进行处理为接近2次幂的那个数

[4] 现在看第29行,就是在这里真正对HashMap里面的数组进行初始化的

(第[3]、[4]步为第一次使用刚new出来的HashMap时,进行扩容的时候才触发的情况,这种情况比较简单到这里扩容也就结束了。下面是在第2、3、4、5、6…n次扩容的情况)

HashMap扩容完了,下面就是之前的K-V在新的HashMap中的分配问题

[5] 这里就到重点了,在扩容之前已经存放K-V键值对是否还要重新计算并分配到扩容之后的HashMap中,我认为这里分为两种情况,第一种当之前桶中的结点个数只有一个的时候,他是重新计算了一下映射到扩容之后对应的桶的位置也就是在第37行,第二种情况是之前桶中的节点个数大于一个的时候,是不需要重新计算的,这里又分两种情况,第一种桶中现在是链表,第二种桶中为红黑树。先对链表的时候分析,第41~68行,对该链表一分为而,注意扩容前桶中的结点分为两种,一种是依旧在之前那个桶对应的下标的桶中,另一种是之前所在的桶的下标+oldCap ,分开的条件是该结点的K的hash值与扩容之前的总桶数n做了一个&运算(以前做映射的时候的公式为hash&(n-1)n为总桶数注意两个公式区别),为什么要使用这个条件分为两个链表,主要是判断出扩容之后哪些结点依旧在之前那个桶对应的下标的桶中,哪些结点在之前所在的桶的下标+oldCap的桶中原理如下图
在这里插入图片描述
[6] 分析完链表的情况,再分析红黑树的情况,看链表扭转为红黑树的源码就知道其实这里的红黑树不仅仅是红黑树(以后会写链表扭转红黑树博客分析),而且还是具有双向链表的结构,这里也跟[5]过程原理一样,就不多说了。

感谢各位的观看,如果哪里有不对的地方希望指出来,及时改正。

评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值