1. 程式人生 > >HashMap原始碼解析jdk1.8:初始化resize,新增put,獲取get

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