JDK8 HashMap源码分析

1. putVal方法

该方法主要做以下几件事:

(1)  首先判断HashMap底层的table是否初始化,如果没有,就调用resize()方法进行初始化table操作. 注意resize方法即可以初始化table操作,也可以对table进行扩容 

(2) 根据当前key的hash值和table的size值,计算key对应的valu值应该存储在table表中的下标值,记为i

(3) 如果table[i]为空,就创建一个Node节点(节点封装了key,value相关的数据)存放在table[i]上

(4) 如果table[i]已经有值了,我们将该值记为p,注意这个p肯定table表中的元素,同时也可能是链表中的头节点, 这又分成3种情况处理

  <1> 如果key与p节点的key完全相等,那就覆盖oldValue

  <2> 如果p是一颗树.....(跳过,没看懂)

  <3> 除去上面两种情况之外,P的屁股后面肯定挂着一个链表,  这就需要对链表中的每个元素进行遍历,判断当链表中的节点key与当前put的key是否相等,如果相等,也是将oldValue进行覆盖,否则就是new一个新的Node节点,然后挂在链表的屁股后面. 同时也会对这个链表的节点长度进行判断,如果超过8,则会调用treeifyBin方法,进行链表转树的操作

(5) 源码

/**
*    Map put方法的实现
*/
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,boolean evict) {
    Node<K,V>[] tab; Node<K,V> p; int n, i;
    
    // 如果table是null,就是还没有初始化(jdk8中, table是在第一次使用的时候初始化的)
    if ((tab = table) == null || (n = tab.length) == 0)
        // resize()方法对table进行初始化或者2倍扩容
        n = (tab = resize()).length;
        
    // table的length减1与当前key的hash值的与运算,即是这个key在table中存储下标
    // & 运行, 二进制位数都是1结果才是1,否则是0
    // table中是否存储着当前key对应的value值
    if ((p = tab[i = (n - 1) & hash]) == null)  // 不存在这个key,如果存在,就挂链表
        // 创建一个新的Node,存储到table表中下标为i的slot位置
        tab[i] = newNode(hash, key, value, null);
   
   else {// 挂链表
        
        Node<K,V> e; K k;
        
        // 1. 对table中的数据进行覆盖判断,因为p是链表的头节点,是存放在table中的
        if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k))))
            e = p;  // 将覆盖前的p赋值给e, 注意:这儿并没有进行覆盖
            
        // 2. 处理树的情况(先跳过)
        else if (p instanceof TreeNode)
            e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
        
        // 3. 链表的情况,有hash冲突的数据,直接挂在原节点的next上
        else {
            for (int binCount = 0; ; ++binCount) {
            
                if ((e = p.next) == null) { 
                    // 将key,value封装成一个Node节点,然后挂在p的next上
                    p.next = newNode(hash, key, value, null);
                    
                    if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                        // 链表转树结构
                        treeifyBin(tab, hash);
                    break;
                }
            
                // 对链表节点中数据进行覆盖判断,注意前面有段相同的代码,那是对table中的数据(链表头节点)进行覆盖操作
                if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) 
                    break;//  如果key相同,break跳出for循环,执行后面的逻辑
                p = e;
            }
        }
        // 存在映射关系的key
        if (e != null) { // existing mapping for key
            V oldValue = e.value;
            if (!onlyIfAbsent || oldValue == null)
                e.value = value;  // 用新的value值去覆盖老的value值
            afterNodeAccess(e);
            return oldValue;  // 返回覆盖前的value值
        }
    }
    // 记录HashMap修改的次数
    ++modCount;
    // 记录key-value映射的次数,相当于HashMap的size
    if (++size > threshold)  // 如果size大于threshold,就需要进行扩容
        resize();
    // 移除更老的数据, 这里暂时不看
    afterNodeInsertion(evict);
    return null; // 返回null
}

2. resize方法

  该方法有两个作用,一是对table进行初始化操作,一是对table进行扩容操作. 原则上每次扩容2倍.  这个方法的重点是看它如何将oldTab中的元素转移到newTab中去的.

  源码使用了for循环,遍历出oldTab中的每一个元素,我们记为e, 然后再对e的相关属性进行判断, 同样分为3种情况

(1) 如果e.next==null, 表明e节点屁股后面即没跟树,也没跟链表,即是e.key无hash冲突的情况. 这样情况最简单, 通过计算e.hash值然后& 扩容后的table长度,即为e在newTab中的存放位置

(2) e节点就是一颗树的情况, 跳过

(3) e屁股后面挂着链表的情况,也没看太懂

  源码显示,通过 e.hash & oldCap 将e屁股后面挂的链表拆分成了两个链表, 然后将这两个新的链表分析挂在newTab的两个槽位上. 这儿比较神奇,原本处于同一个链表结构的数据(oldTab),有hash冲突, 现在通过扩容,挂在了newTab的两个槽位上,表明这两个槽位的中key不存在hash冲突了, 这是不是从侧面说明了,扩容减少了hash冲突的机率.

源码

// 对HashMap底层table进行初始化或者扩容
final Node<K,V>[] resize() {
    // 1. 将原先的table赋值给变量oldTab
    Node<K,V>[] oldTab = table;
    // oldTab的容量值,即原table中有多少个元素
    int oldCap = (oldTab == null) ? 0 : oldTab.length;
    //  原先扩容的阈值
    int oldThr = threshold;
    // 定义了两个变量,新的table的容量和阈值
    int newCap, newThr = 0;
    if (oldCap > 0) { // 表示原table中有元素
        if (oldCap >= MAXIMUM_CAPACITY) {  // 如果原来table(扩容前)的元素个数大于等于 1073741824
            threshold = Integer.MAX_VALUE;  // 直接将阈值设置为Integer的Max_VALUE值
            return oldTab;
        }
        // newCap在 oldCap的基础扩容1倍
        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 = 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);
    }
    threshold = newThr;
    
    
    // 这儿就是第一次使用时,对table进行初始化
    Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
    table = newTab; // 将初始化的这个newTab赋值到table
    
    
    
    // 下面应该是重点: 扩容(将扩容前table中的元素移动到扩容后的table中去).   如果是初始化, 不会进入if条件里面去
    if (oldTab != null) {
        // 通过for循环遍历取出扩容前table中的每个元素
        for (int j = 0; j < oldCap; ++j) {
            Node<K,V> e;
             // 只对非空元素进行处理
            if ((e = oldTab[j]) != null) {   // 将oldTab中的j号位置的元素取出来赋值给e这个变量
                oldTab[j] = null;    // 将oldTab中j号位置置空
                
                // 下面这段逻辑就是将扩容前table中的元素移动到扩容后table, 具体分为3种情况
                //1 . 第一种情况,也是最简单的, 无hash冲突,也就是说无链表
                if (e.next == null) 
                    newTab[e.hash & (newCap - 1)] = e;  // 将e这个元素放到newTab(扩容后的table)的  e.hash & (newCap - 1) 这个位置
                
                // 有hash冲突,并且后面的链表已经转成了红黑树
                else if (e instanceof TreeNode)
                    ((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
                
                // 有hash冲突,但后边还是链表
                else { 
                    Node<K,V> loHead = null, loTail = null;
                    Node<K,V> hiHead = null, hiTail = null;
                    Node<K,V> next;
                    
                    // 这个do...while循环将oldTab[j]元素后面链表中的节点分别挂在两个链表上,一个是lo...., 一个是hi...., 
                    // 然后将lo,hi两个拆分出来的链表挂在扩容后的newTab的不同位置上
                    // 感觉还是没看懂...
                    do {
                        next = e.next;
                        if ((e.hash & oldCap) == 0) { // lo.. 链表
                            if (loTail == null)
                                loHead = e;
                            else
                                loTail.next = e;
                            loTail = e;
                        
                        }else {   // hi..链表
                            
                            if (hiTail == null)
                                hiHead = e;
                            else
                                hiTail.next = e;
                            hiTail = e;
                        }
                    } while ((e = next) != null);
                    
                    
                    if (loTail != null) {
                        loTail.next = null;
                        newTab[j] = loHead;  // 将lo链表挂到newTab[j]位置上
                    }
                    if (hiTail != null) {
                        hiTail.next = null;
                        newTab[j + oldCap] = hiHead;  // 将hi链表挂到newTab[j + oldCap]位置上
                    }
                }
            }
        }
    }
    return newTab;
}

 未完等续.....

  

  

  

猜你喜欢

转载自www.cnblogs.com/z-qinfeng/p/12115647.html