jdk1.8中hashmap的扩容步骤resize

Posted 小大白日志

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了jdk1.8中hashmap的扩容步骤resize相关的知识,希望对你有一定的参考价值。

当hashmap第一次插入元素、元素个数达到容量阀值threshold时,都会扩容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)  //注释1
13                 newThr = oldThr << 1; // double threshold
14         }
15         else if (oldThr > 0) // initial capacity was placed in threshold
16             newCap = oldThr;
17         else {               // zero initial threshold signifies using defaults
18             newCap = DEFAULT_INITIAL_CAPACITY;
19             newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
20         }
21         if (newThr == 0) {
22             float ft = (float)newCap * loadFactor;
23             newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
24                       (int)ft : Integer.MAX_VALUE);
25         }
26         threshold = newThr;
27         @SuppressWarnings({"rawtypes","unchecked"})
28             Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
29         table = newTab;
30         if (oldTab != null) {
31             for (int j = 0; j < oldCap; ++j) {  //注释2
32                 Node<K,V> e;
33                 if ((e = oldTab[j]) != null) {//注释3
34                     oldTab[j] = null;
35                     if (e.next == null)       //注释4
36                         newTab[e.hash & (newCap - 1)] = e;
37                     else if (e instanceof TreeNode) //注释5
38                         ((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
39                     else { // preserve order        //注释6
40                         Node<K,V> loHead = null, loTail = null; //注释7
41                         Node<K,V> hiHead = null, hiTail = null;//注释8
42                         Node<K,V> next;
43                         do {
44                             next = e.next;
45                             if ((e.hash & oldCap) == 0) { //注释9
46                                 if (loTail == null)  //注释10
47                                     loHead = e;
48                                 else
49                                     loTail.next = e; 
50                                 loTail = e;         
51                             }
52                             else {
53                                 if (hiTail == null)//注释11
54                                     hiHead = e;
55                                 else
56                                     hiTail.next = e;
57                                 hiTail = e;
58                             }
59                         } while ((e = next) != null);
60                         if (loTail != null) {  //注释12
61                             loTail.next = null;
62                             newTab[j] = loHead;
63                         }
64                         if (hiTail != null) {//注释13
65                             hiTail.next = null;
66                             newTab[j + oldCap] = hiHead;
67                         }
68                     }
69                 }
70             }
71         }
72         return newTab;
73     }
注释1:对新横向node数组的长度和容量阀值均扩容为2倍
注释2:遍历旧横向node数组中每个node节点,在后面的代码中对每个node节点重新分配下标位置到新横向node数组
注释3:旧横向node数组中当前节点为空,则跳过
注释4:旧横向node数组中当前节点若只有一个元素(构不成node链表、红黑树),则直接给该node节点分配下标位置到新横向node数组
注释5:旧横向node数组中当前节点是红黑树节点,则直接调用红黑树的split()方法对红黑树进行修剪
注释6:旧横向node数组中当前节点是普通链表头节点,则为该链表重新分配下标到新横向node数组
注释7:旧横向node数组中当前节点下标=重新分配的新横向node数组中当前节点下标,则用loHead 表示旧横向node数组中当前node节点(即链表头),用loTail 表示旧横向node数组中当前node节点的链表尾
注释8:与注释7相反,旧横向node数组中当前节点下标 不等于 重新分配的新横向node数组中当前节点下标,hiHead表示新横向node数组的链表头节点,hiTail 表示新横向node数组的链表尾节点
注释9:重点,见下图。当“hash值”和“容量”进行与操作时,若结果为0,则当前节点在新旧横向node数组下标相等;否则若结果不为0,则当前节点在新横向node数组下标=该节点在旧横向的下标+旧容量(即旧横向node数组的长度)
注释10:当前节点在新旧横向node数组下标相等,用loTail 、loHead 标识
注释11:当前节点在新旧横向node数组下标不相等,用hiTail、hiHead 标识
注释12:loTail不为空,代表当前节点在新旧横向node数组下标相等
注释13:hiTail 不为空,代表当前节点在新旧横向node数组下标不相等,即当前节点在新横向node数组下标=旧横向的下标+旧容量
 

 

以上是关于jdk1.8中hashmap的扩容步骤resize的主要内容,如果未能解决你的问题,请参考以下文章

jdk1.8 HashMap源码分析(resize函数)

JDK1.7中HashMap死环问题及JDK1.8中对HashMap的优化源码详解

jdk1.8 HashMap扩容原理解析

jdk1.8 HashMap底层数据结构:深入解析为什么jdk1.8 HashMap的容量一定要是2的n次幂

面试常问的HashMap源码分析(jdk1.8)

HashMap中的resize问题