HashMap源码分析2:扩容

本文源码基于JDK1.8.0_45。

 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     //处理数组已经初始化过的情况
 7     if (oldCap > 0) {
 8         //达到了最大的容量无法再扩容,结束扩容流程
 9         if (oldCap >= MAXIMUM_CAPACITY) {
10             threshold = Integer.MAX_VALUE;
11             return oldTab;
12         }
13         //容量和扩容的阈值都变为原来的两倍
14         else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
15                     oldCap >= DEFAULT_INITIAL_CAPACITY)
16             newThr = oldThr << 1; // double threshold
17     }
18     //指定容量的初始化,用扩容的阈值作为中转定义了初始容量
19     else if (oldThr > 0) // initial capacity was placed in threshold
20         newCap = oldThr;
21     //未指定容量初始化,采用默认的容量和阈值
22     else {               // zero initial threshold signifies using defaults
23         newCap = DEFAULT_INITIAL_CAPACITY;
24         newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
25     }
26     //未指定新的阈值时用容量计算阈值(可以看到在JDK1.8的源码中,只有指定容量的初始化时才会进入下面的逻辑)
27     if (newThr == 0) {
28         float ft = (float)newCap * loadFactor;
29         newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
30                     (int)ft : Integer.MAX_VALUE);
31     }
32     threshold = newThr;
33     @SuppressWarnings({"rawtypes","unchecked"})
34         Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
35     table = newTab;
36     //以上是创建新数组的逻辑,下面再来看HashMap中原来存的数据怎么重新分配到新的数组中
37     if (oldTab != null) {
38         //根据数组下标遍历数组
39         for (int j = 0; j < oldCap; ++j) {
40             Node<K,V> e;
41             //如果该下标指向的头节点为空可以直接跳过
42             if ((e = oldTab[j]) != null) {
43                 oldTab[j] = null;
44                 //如果只有一个节点,则将该节点放入新的数组对应的位置
45                 if (e.next == null)
46                     newTab[e.hash & (newCap - 1)] = e;
47                 //如果该节点是树节点,则调用红黑树的方法进行转移
48                 else if (e instanceof TreeNode)
49                     ((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
50                 //最后是链表的情况
51                 else { // preserve order
52                     Node<K,V> loHead = null, loTail = null;//数组下标不需要更改的节点放在这个链表里
53                     Node<K,V> hiHead = null, hiTail = null;//数组下标需要更改的节点放在这个链表里
54                     Node<K,V> next;
55                     do {
56                         next = e.next;
57                         //以容量为16为例,则计算下标的方式是用哈希值与15做与运算。15的二进制为1111,即二级制数保留低4位的值
58                         //假设当前扩容容量为32,则要与31做与运算。31的二级制为11111,即保留二进制数低5位的值
59                         //如果哈希值与当前容量即16(二进制10000)做与运算等于0,表示该哈希值在新增的第五位为0,扩容后不影响其下标的计算
60                         //因此这部分数据在新的数组中位于相同的位置
61                         if ((e.hash & oldCap) == 0) {
62                             if (loTail == null)
63                                 loHead = e;
64                             else
65                                 loTail.next = e;
66                             loTail = e;
67                         }
68                         //与上述相反,如果与运算结果为1,表示其哈希值有影响,且下标移动的位置刚好是oldCap的值
69                         else {
70                             if (hiTail == null)
71                                 hiHead = e;
72                             else
73                                 hiTail.next = e;
74                             hiTail = e;
75                         }
76                     } while ((e = next) != null);
77                     //与原数组下标相同
78                     if (loTail != null) {
79                         loTail.next = null;
80                         newTab[j] = loHead;
81                     }
82                     //下标的为原数组下标 + oldCap
83                     if (hiTail != null) {
84                         hiTail.next = null;
85                         newTab[j + oldCap] = hiHead;
86                     }
87                 }
88             }
89         }
90     }
91     return newTab;
92 }

本文同样忽略了扩容时红黑树的实现,这些内容将在之后介绍。

猜你喜欢

转载自www.cnblogs.com/lwpimis/p/10573550.html