HashMap在1.7和1.8的扩容,源码分析

文章详细阐述了Java中HashMap在JDK7和JDK8版本下的扩容机制,包括初始容量、最大容量、负载因子以及扩容条件。当元素数量超过阈值时,HashMap会进行扩容,扩容通常是将容量翻倍。在扩容过程中,既有对链表的处理,也有对红黑树的处理,以确保数据的正确迁移。
摘要由CSDN通过智能技术生成
一般情况下,第一次添加元素或元素数量超过阈值时便会触发扩容
每次扩容的都是之前容量的2倍,第一次默认是16
容量有上限,必须小于 1<<30,即1073741824。若容量超出了这个 数,则不再增长,且阈值会被设置为 Integer.MAX_VALUE

JDK7中
空参数的构造函数:以默认容量、默认负载因子、默认阈值初始化数组。内部数组是空数组
有参构造函数:根据参数确定容量、负载因子、阈值等
第一次put时会初始化数组,其容量变为不小于指定容量的2的幂数,然后根据负载因 子确定阈值
如果不是第一次扩容,则 新容量=旧容量 x 2 ,新阈值=新容量 x 负载因子

JDK8
空参构造:实例化HashMap默认内部数组是null,即没有实例化。第一次调用put()时开始第一次初始化扩容,长度为 16
有参构造函数:用于指定容量。会根据指定的正整数找到不小于指定容量的2的幂数, 将这个数设置赋值给阈值(threshold)。第一次调用put()时,会将阈值赋值给容量, 然后让 阈值 = 容量 x 负载因子
若不是第一次扩容,则容量变为原来的2倍,阈值也变为原来的2倍。(容量和阈值都变为原来的2倍时,负载因子还是不变)

此外还有几个细节需要注意
首次put时,先会触发扩容(算是初始化),然后存入数据,然后判断是否需要扩容;
不是首次put,则不再初始化,直接存入数据,然后判断是否需要扩容

扩容是不会从新计算hash值的,因为已经缓存在每一个节点中了

resize()扩容方法

final Node<K,V>[] resize() {
    // 旧数组
    Node<K,V>[] oldTab = table;
    // 第一次添加元素第一次扩容oldTab是null,oldCap是0,要不然返回当前数组的长度
    int oldCap = (oldTab == null) ? 0 : oldTab.length;
    // 旧容量对应的需要扩容的阈值
    int oldThr = threshold;
    // 新容量,新容量对应的需要扩容的阈值
    int newCap, newThr = 0;
    if (oldCap > 0) {                       // 旧的map有元素                                              
        if (oldCap >= MAXIMUM_CAPACITY) {   // 如旧map的容量大于1<<30,设置下次扩容的阈值,
            threshold = Integer.MAX_VALUE;
            return oldTab;
        }
        // 如果扩容后的新容量小于HashMap最大容量,且旧容量大于等于初始化容量
        // 这块也可看到HashMap的扩容原理,即newCap=oldCap << 1,也就是2倍
        else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
                 oldCap >= DEFAULT_INITIAL_CAPACITY)
            // 新阈值也是旧阈值的二倍     
            newThr = oldThr << 1; // double threshold
    }
    else if (oldThr > 0) // initial capacity was placed in threshold
        newCap = oldThr;
    else {               // zero initial threshold signifies using defaults
        // 第一次添加元素newCap是16
        newCap = DEFAULT_INITIAL_CAPACITY;
        // 下一次扩容需要达到的元素个数
        newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
    }
    if (newThr == 0) {
        float ft = (float)newCap * loadFactor;
        newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
                  (int)ft : Integer.MAX_VALUE);
    }
    // 把newThr值给threshold,下一次扩容需要达到元素个数
    threshold = newThr;
    @SuppressWarnings({"rawtypes","unchecked"})
         // 第一次添加元素初始化数组大小是16
        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) {  // 链表不是空的,用变量e存储链表
                oldTab[j] = null;           //  清空这个索引的数据
                if (e.next == null)         // 链表到了最后
                    newTab[e.hash & (newCap - 1)] = 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;
                        // oldCap是2的幂次方,e.hash&oldCap的结果要么是0,要么是oldCap
                        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;  // 低位链表位置
                    }
                }
            }
        }
    }
    // 如果是第一次添加元素,直接返回16大小的数组
    return newTab;
}
 

===========================================

jdk1.7扩容时候有线程安全死循环问题

void transfer(Entry[] newTable, boolean rehash) {
    int newCapacity = newTable.length;
    // 遍历数组
    for (Entry<K,V> e : table) {
        // 存有数据,不为空
        while(null != e) {
            // 存储下一个节点
            Entry<K,V> next = e.next;
            // 是否重新计算hash,不需要,
因为已经缓存了
            if (rehash) {
                e.hash = null == e.key ? 0 : hash(e.key);
            }
            // hash在新的数组中的索引
            int i = indexFor(e.hash, newCapacity);
            // 当前节点的下一个指向新数组的索引位置
            e.next = newTable[i];            
            // 让新数组的新index的位置的元素是e,也就是把e节点迁移到了新的链表上
            newTable[i] = e; // *线程1在这行暂停(尚未执行该行)
            //  循环下一个节点
            e = next;
        }
    }
}

多线程扩容时,因为共享原来的数据,扩容后会形成循环链表
在get()时get这个链表上不存在的数据,就会死循环在环形链表中

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值