天天看點

HashMap源碼解析jdk1.8:初始化resize,添加put,擷取get源碼解析有參考以下部落格:

源碼解析有參考以下部落格:

http://www.cnblogs.com/jzb-blog/p/6637823.html

HashMap:

  以k-v鍵值對存儲格式的容器,key,value都可以為空,key不重複,非線程安全(線程安全請使用ConcurrentHashMap);

  底層采用的是 數組+(連結清單 / 紅黑樹)結構組成; 常用的有put(),get(),size(),remove(),entrySet()等方法。

 下面是對:初始化resize,put,get的源碼解析,都在源碼中每一步中均有注釋說明

 初始化resize:   
final Node<K,V>[] resize() {
    Node<K,V>[] oldTab = table;
    int oldCap = (oldTab == null) ? 0 : oldTab.length; // 舊數組大小
    int oldThr = threshold;                            // 舊數組容量
    int newCap, newThr = 0;                            // 新數組大小和容量
    // 1.舊數組大小大于0 (新數組大小及容量均擴大兩倍 或 新數組大小擴大兩倍 或 無需擴容)
    if (oldCap > 0) {
        if (oldCap >= MAXIMUM_CAPACITY) {              // 若舊數組大小>=最大容量
            threshold = Integer.MAX_VALUE;             // 數組容量為Integer最大值
            return oldTab;
        }
        // 新數組大小擴大二倍,
        else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY && 
                 oldCap >= DEFAULT_INITIAL_CAPACITY)
            // 若newCap小于最大容量, 舊數組大小>=16 則新容量擴大二倍
            newThr = oldThr << 1; // double threshold
    }
    // 2.舊數組容量大于0 (新數組大小等于舊容量,未涉及新容量)
    else if (oldThr > 0) // initial capacity was placed in threshold
       // 新大小 = 舊容量
        newCap = oldThr; 
    // 3.使用預設值(新數組大小預設值16,新容量=16*0.75(預設負載因子)=12)
    else {               // zero initial threshold signifies using defaults
        newCap = DEFAULT_INITIAL_CAPACITY;
        newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
    }
    // 若新數組容量=0(1和2 會導緻newThr==0)
    if (newThr == 0) {
        // 新容量預期值 = 新數組大小*負載因子
        float ft = (float)newCap * loadFactor;
        // 若新數組大小和ft都小于最大容量 則新容量=ft,否則等于Integer最大值
        newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
                  (int)ft : Integer.MAX_VALUE);
    }
    // 容量的值 = 新容量
    threshold = newThr;
    // newCap值為新數組大小建立node數組
    @SuppressWarnings({"rawtypes","unchecked"})
        Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
    // table引用newTab
    table = newTab;
    if (oldTab != null) {
        // 省略不做分析 :在新數組中添加舊資料

    } 
}
           
添加元素put

 1.計算hash值并調用putVal方法

public V put(K key, V value) {
    return putVal(hash(key), key, value, false, true);
}
           

2.判斷數組是否為空,如果為空,初始化table數組

3.添加資料

final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {
        // tab是指将要操作的數組引用
        // p表示tab上hash值對應的Node節點
        // n表示tab長度,i表示p在tab下标
        Node<K,V>[] tab; Node<K,V> p; int n, i;
        // table為存儲資料的數組
        if ((tab = table) == null || (n = tab.length) == 0)
            // 2.初始化
            n = (tab = resize()).length;
        // 3.添加資料
        // 判斷key所在的Node數組元素p是否為空
        if ((p = tab[i = (n - 1) & hash]) == null)
            // 3.1 p == null 隻需建立一個Node即可
            tab[i] = newNode(hash, key, value, null);
        else {
            // e: p中node節點(表示每次p.next的Node),最終表示即将添加的key,value
            // k: p中node節點(e)的key
            HashMap.Node<K,V> e; K k;
            // 3.2 p != null
            if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k))))
                // 3.2.1 p第一個節點的key就等于添加的key,直接傳回p
                e = p;
            else if (p instanceof HashMap.TreeNode)
                // 3.2.2 p是紅黑樹----todo
                e = ((HashMap.TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
            else {
                // 3.2.3 周遊連結清單結構的p,每次擷取p的下一個節點Node,并記錄binCount(标記用于轉換紅黑樹)
                for (int binCount = 0; ; ++binCount) {
                    // 3.2.4 p.next == null
                    if ((e = p.next) == null) { // p.next 表示擷取p的下一個Node
                        // 表示已經到達節點末尾,隻需要建立key,value的Node對象,并将p.nexc指向它,退出循環
                        p.next = newNode(hash, key, value, null);
                        // 若p節點大于等于8,将連結清單結構轉化為紅黑樹
                        // (條件比較>=7,因為binCount從0開始計數的) ----todo
                        if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                            treeifyBin(tab, hash);
                        break;
                    }
                    // 3.2.5 p.next.key == key (e.key等于添加的key,退出循環)
                    if (e.hash == hash &&
                            ((k = e.key) == key || (key != null && key.equals(k))))
                        break;
                    // 重置p,不然的話每次p.next都是p的第二個節點(因為e = p.next,加上p = e,就相當于p = p.next)
                    p = e;
                }
            }
            // 3.2.6 将value替換e中的value
            if (e != null) { // existing mapping for key
                V oldValue = e.value;
                if (!onlyIfAbsent || oldValue == null)
                    e.value = value;
                // 這個函數在hashmap中沒有任何操作,是個空函數,他存在主要是為了linkedHashMap的一些後續處理工作。
                // 引用http://www.cnblogs.com/jzb-blog/p/6637823.html 原話
                afterNodeAccess(e);
                return oldValue;
            }
        }
        // 資料結構變化
        ++modCount;
        if (++size > threshold)
            // 前面提到過threshold是數組容量 若超過需要擴容
            resize();
        // 這裡與前面的afterNodeAccess同理,是用于linkedHashMap的尾部操作,HashMap中并無實際意義。
        // 引用http://www.cnblogs.com/jzb-blog/p/6637823.html 原話
        afterNodeInsertion(evict);
        return null;
    }
           
擷取get 

   通過hash值找到在table數組中的那個Node節點(該節點存有大于等于8個元素,則轉化為紅黑樹,發生在put操作),

   紅黑樹:通過樹形結構查找     連結清單:循環比較連結清單每一個Node的key

final HashMap.Node<K,V> getNode(int hash, Object key) {
            // table數組  first:hash值對應的Node  e:first中的其中一個 
            // n:tab數組長度  k:first中的key
            HashMap.Node<K,V>[] tab; HashMap.Node<K,V> first, e; int n; K k;
            // 1.查詢hash值對應的node節點 為null直接傳回null
            if ((tab = table) != null && (n = tab.length) > 0 &&
                    (first = tab[(n - 1) & hash]) != null) {
                // 2.該hash位置的Node第一個key等于需要查詢的key 傳回該節點
                if (first.hash == hash && // always check first node
                        ((k = first.key) == key || (key != null && key.equals(k))))
                    return first;
                // 3.擷取first下一個node
                if ((e = first.next) != null) {
                    // 3.1 若是樹 通過樹形結構擷取key對應的Node
                    if (first instanceof HashMap.TreeNode)
                        return ((HashMap.TreeNode<K,V>)first).getTreeNode(hash, key);
                    // 3.2 否則循環比較first連結清單每一個Node的key
                    do {
                        if (e.hash == hash &&
                                ((k = e.key) == key || (key != null && key.equals(k))))
                            return e;
                    } while ((e = e.next) != null);
                }
            }
            return null;
        }
           

        面試必問的源碼問題,今天抽出時間也把源碼了解以下并釋出文章。希望有描述不準确,了解不到位的地方,

非常感謝各位,能夠指點一下,有您的指點我定會更加深刻,非常感謝花費您寶貴的時間看我這篇文章。

再次注明參考部落格:

http://www.cnblogs.com/jzb-blog/p/6637823.html