不同hashMap的扩容总结(一) | 青训营笔记

53 阅读2分钟

这是我参与「第四届青训营 」笔记创作活动的第3天

在学习redis时整好好奇,这几天就专门研究这玩意把

HashMap(无红黑树,因为不会)

扩容时机

在putval方法中出现了两次,还有一次出现在treeifyBin方法上

  • put方法发现数组大小为0时调用resize进行初始化
  • 当数组大小大于阈值时(阈值=容量*加载因子)
  • 当前桶的元素数量大于8个,桶数量小于64

扩容原理

扩容大小为两倍

核心源码

这里去掉了第一次创建,和一些大小变化

final Node<K,V>[] resize() {
    Node<K,V>[] oldTab = table;
    
    //扩容两倍
    if (oldCap > 0) {
            newThr = oldThr << 1; 
    }
    Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
    table = newTab;
    if (oldTab != null) {
        for (int j = 0; j < oldCap; ++j) {
            Node<K,V> e;
            if ((e = oldTab[j]) != null) {
                //将旧桶赋空
                oldTab[j] = null;
                if (e.next == null)
                    //重新计算hash,且旧桶为单个,不成链情况
                    newTab[e.hash & (newCap - 1)] = e;
                 
                //有子链,链分为高低位的头和尾
                else { 
                    Node<K,V> loHead = null, loTail = null;
                    Node<K,V> hiHead = null, hiTail = null;
                    Node<K,V> next;
                    do {
                        next = e.next;
                        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;
                        newTab[j + oldCap] = hiHead;
                    }
                }
            }
        }
    }
    return newTab;
}

关键思想点

1、高低链

一次扩容就是两倍大小,rehash之后,就只可能出现在两个位置

如:17%16=1,33%16=1---扩容--->17%32 = 17,33%32 = 1

2、尾插法防止成环

详细参考BV1n541177Ea
jdk1.7之前是头插法 多线程的扩容下

  1. 两个线程同时进到transfer方法

image.png
2. 线程e1优先tranfer完

image.png


3. 线程e2唤醒后

image.png

  1. 最后当e2为2时,e2.next又是3,又将3赋值给了新桶的头元素,成环。e2指针从唤醒到结束:3--2--3

image.png