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 }
本文同样忽略了扩容时红黑树的实现,这些内容将在之后介绍。