Java集合之Map

HashMap

數組(Node類型的數組)加鏈表(或者紅黑樹),new HashMap時不指定大小,則默認爲空,在第一次put數據時候會對Node數組進行擴容,默認大小爲16,擴容是耗費性能的,所以阿里手冊中創建HashMap時候需要指定容量大小

transient Node<K,V>[] table;

通過計算存入對象的Hash值來計算在數組的索引位置

當發生Hash衝突時候,衝突的索引位置會以鏈表的形式解決衝突

        final int hash;
        final K key;
        V value;
        Node<K,V> next;

        Node(int hash, K key, V value, Node<K,V> next) {
            this.hash = hash;
            this.key = key;
            this.value = value;
            this.next = next;
        }

如果鏈表的長度大於8,會嘗試把鏈表轉化爲紅黑樹。

問題:鏈表的長度有沒有可能大於8?

是存在這種情況的

因爲升級爲紅黑樹時會調用treeifyBin方法,該方法會判讀當前map的size是否小於64,如果小於64,會先對table進行擴容,如果大於64則轉換爲紅黑樹;因此當前map的size是否小於64是存在鏈表的長度大於8的情況的。

final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {
        ....
                        if (binCount >= TREEIFY_THRESHOLD - 1) //8-1
                            treeifyBin(tab, hash);
                   .....
    final void treeifyBin(Node<K,V>[] tab, int hash) {
        int n, index; Node<K,V> e;
        // 如果小於64則先resize擴容
        if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)
            resize();
        // 否則纔會轉爲紅黑樹
        else if ((e = tab[index = (n - 1) & hash]) != null) {
            TreeNode<K,V> hd = null, tl = null;
            do {
                TreeNode<K,V> p = replacementTreeNode(e, null);
                if (tl == null)
                    hd = p;
                else {
                    p.prev = tl;
                    tl.next = p;
                }
                tl = p;
            } while ((e = e.next) != null);
            if ((tab[index] = hd) != null)
                hd.treeify(tab);
        }
    }

爲什麼HashMap的key是亂序的呢?

因爲對HashMap取key時,它是按照數組table的順序進行取值的,而數組table所存儲元素的位置又是根據Hash算出來的,所以導致亂序,取key時候如下圖:

即遍歷數組,如果元素爲鏈表,把該數組索引位置的鏈表遍歷完成後,在遍歷數組中下一個元素。

如果想要使用key按照添加順序的map的話,可以使用下面的LinkedHashMap。

 

LinkedHashMap

繼承於HashMap,構造方法如下:

public LinkedHashMap(int initialCapacity,
                     float loadFactor,
                     boolean accessOrder) {
    super(initialCapacity, loadFactor);
    this.accessOrder = accessOrder;
}

前兩個參數同HashMap;

accessOrder:是否按照訪問次數排序,默認爲false,false則按照插入次序排序。

接下來我們看它是如何保證順序不變的,首先看put方法,它的put方法還是使用的HashMap的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;
        if ((tab = table) == null || (n = tab.length) == 0)
            n = (tab = resize()).length;
        if ((p = tab[i = (n - 1) & hash]) == null)
            tab[i] = newNode(hash, key, value, null);// newNode是重點
        else {
            ...
        }
     ....
  }

其中LinkedHashMap對這個方法進行了重寫

    Node<K,V> newNode(int hash, K key, V value, Node<K,V> e) {
        LinkedHashMap.Entry<K,V> p =
            new LinkedHashMap.Entry<K,V>(hash, key, value, e);
        linkNodeLast(p);
        return p;
    }

再看linkNodeLast方法,該方法每次添加新的節點都會添加到原鏈表的末端

    private void linkNodeLast(LinkedHashMap.Entry<K,V> p) {
        LinkedHashMap.Entry<K,V> last = tail;
        tail = p;
        if (last == null)
            head = p;
        else {
            p.before = last;
            last.after = p;
        }
    }

發現了head和tail對象,LinkedHashMap維護了兩個成員變量,是雙向鏈表

    transient LinkedHashMap.Entry<K,V> head;

    transient LinkedHashMap.Entry<K,V> tail;

這兩個雙向列表在每次進行put操作時候都會進行更新,按照插入順序不斷在鏈表末尾添加新的數據,從而保證了數據的次序性,從而達到獲取的key是按順序顯示的。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章