HashMap的扩容机制 上一期已经讲到了添加元素的put方法了,现在回顾一下put方法,主要讲解扩容方法:
调用的是putVal方法:
经过插入元素插入之后,改变桶的大小,判断是否超过阀值(阀值 = 加载因子 * 桶容量),超过就扩容:
final Node<K,V>[] resize() { Node<K,V>[] oldTab = table; // oldTable 为null的话说明这个HashMap还没被初始化过 int oldCap = (oldTab == null) ? 0 : oldTab.length; // 用来判断是否扩容的阀值,如果没被初始化过的HashMap这个oldCap是为0的 int oldThr = threshold; int newCap, newThr = 0; // oldCap > 0说明是已经使用过的HashMap现在添加元素导致产生扩容 if (oldCap > 0) { // 旧容量如果大于 MAXIMUM_CAPACITY(1 << 30) if (oldCap >= MAXIMUM_CAPACITY) { // 将阀值赋值为int类型所能标识的最大值 // 由于有阀值存在 使得HashMap提前扩容了 // threshold = Integer.MAX_VALUE 的意思是充分使用空间 threshold = Integer.MAX_VALUE; return oldTab; } // 如果容量左移1位也就是扩大2倍要比MAXIMUM_CAPACITY(1 << 30)小 // 并且要比初始容量DEFAULT_INITIAL_CAPACITY(1 << 4 = 16)要大 else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY && oldCap >= DEFAULT_INITIAL_CAPACITY) // 阀值也进行扩容 并且容量是之前的2倍 newThr = oldThr << 1; // double threshold } else if (oldThr > 0) // initial capacity was placed in threshold // 如果旧的阀值要大于0 也就是在调用非默认的HashMap构造方法时 // 第一次插入数据才会到这儿 newCap = oldThr; else { // zero initial threshold signifies using defaults // 否则的话就是用默认的大小 计算出阀值进行复制 newCap = DEFAULT_INITIAL_CAPACITY; newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY); } if (newThr == 0) { // 如果阀值为0那么就求出阀值然后赋值,小伙伴肯定有疑问:何时阀值才会为0呢? // 如果调用非默认构造方法第一次put操作就会到这里 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) { // 老的桶不为null的话 就转移元素 // 循环遍历 for (int j = 0; j < oldCap; ++j) { Node<K,V> e; // 在oldTab[j]上存在元素 赋值给e if ((e = oldTab[j]) != null) { // help GC oldTab[j] = null; // e元素没有后续节点 if (e.next == null) // 直接计算e在扩容后的新桶上的位置 然后放入 注意:能进到这里每次是第一次 // 访问oldTab中的每个位置的元素,在新桶里定位是不可能存在元素的 所以直接插入 newTab[e.hash & (newCap - 1)] = e; // 如果e是一颗红黑树 else if (e instanceof TreeNode) // 将这颗红黑树分成俩波插入到新桶中 ((TreeNode<K,V>)e).split(this, newTab, j, oldCap); else { // preserve order // 最后到这说明桶所在位置是一个链表 // 将链表拆成俩个链表 分别找到在新桶中的位置 然后插入 Node<K,V> loHead = null, loTail = null; Node<K,V> hiHead = null, hiTail = null; Node<K,V> next; do { next = e.next; // 在链表上的元素和老的容量的与操作只会有俩种情况 // 等于0和不等于0 这样就分成俩个链表了 等于0的还在老位置上 // 这里通过loHead loTail hiHead hiTail 解决了1.7版本及之前的并发死循环问题 // 详情可以看我写的另一篇关于HashMap死循环问题的文章 if ((e.hash & oldCap) == 0) { // 形成链表 if (loTail == null) 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; newTab[j] = loHead; } if (hiTail != null) { hiTail.next = null; // 扩容是原来的俩倍 所以与老容量与操作不为0的元素要搬移,而这个位置 // 正好是当前位置+老的容量 newTab[j + oldCap] = hiHead; } } } } } return newTab;}
以上就是HashMap的扩容机制啦,感谢您宝贵的时间!
今天你只能好看