LinkedHashMap源碼分析(JDK 1.8)

寫在前面

今天給大家帶來的是LinkedHashMap源碼分析,說起這個心中滿滿的痛。記得當初畢業的時候去面試,面試官問HashMap、LinkedHashMap、TreeMap哪些是有序的,我回答了HashMap是有序的。然後就讓我回去等消息了,今天我就來談談自己對LinkedHashMap的理解,也希望能對大家有所幫助。

 

一、繼承關係及主要字段

可以看到LinkedHashMap繼承了HashMap,我前一篇HashMap的文章有提到有個參數是給LinkedHashMap使用的,這一點我後面會解釋。

public class LinkedHashMap<K,V>
    extends HashMap<K,V>
    implements Map<K,V>

主要參數:

    /**
     * 這是linkedHashMap內部的數據結構Entry,它繼承了HashMap的Node數據機構
     */
    static class Entry<K,V> extends HashMap.Node<K,V> {
        //可以看到他多了兩個屬性:一個是before後退指針,一個是after後退指針,所以它其實是一個雙 
        //鏈表
        Entry<K,V> before, after;
        Entry(int hash, K key, V value, Node<K,V> next) {
            super(hash, key, value, next);
        }
    }

   

    /**
     * 雙端鏈表的頭節點.
     */
    transient LinkedHashMap.Entry<K,V> head;

    /**
     * 雙端鏈表的尾節點
     */
    transient LinkedHashMap.Entry<K,V> tail;

    /**
     * 是否按照訪問順序
     *
     * @serial
     */
    final boolean accessOrder;

二、構造方法

/沒什麼特別的,指定初始容量和負載因子,調用父類hashmap的構造方法
    public LinkedHashMap(int initialCapacity, float loadFactor) {
        super(initialCapacity, loadFactor);
        accessOrder = false;
    }

       //指定初始容量,調用父類的構造方法
    public LinkedHashMap(int initialCapacity) {
        super(initialCapacity);
        accessOrder = false;
    }

    
//默認構造,調用父類默認構造
    public LinkedHashMap() {
        super();
        accessOrder = false;
    }

    
    public LinkedHashMap(Map<? extends K, ? extends V> m) {
        super();
        accessOrder = false;
        putMapEntries(m, false);
    }

    //前幾種構造方法accessOrder都初始化爲false,這個構造方法可以自己指定,如果爲true,排序將不    
      //再按照插入順序,而是訪問順序
    public LinkedHashMap(int initialCapacity,
                         float loadFactor,
                         boolean accessOrder) {
        super(initialCapacity, loadFactor);
        this.accessOrder = accessOrder;
    }

三、put元素的過程以及排序原理

我們看代碼會發現,LinkedHashMap並沒有put方法,其實它用的是父類HashMap的put方法,關於父類put方法的詳細介紹可以看我上一篇文章,這邊不再做過多解釋,那問題來了,父類插入節點是HashMap的Node類型,LinkedHashMap怎麼把它變成自己的Entry類型呢?來看代碼:

 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)
            //看這裏:爲什麼HashMap不寫成tab[i] = new Node()而是這樣寫?
            tab[i] = newNode(hash, key, value, null);
        else {
            Node<K,V> e; K k;
            if (p.hash == hash &&
                ((k = p.key) == key || (key != null && key.equals(k))))
                e = p;
            else if (p instanceof TreeNode)
                e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
            else {
                for (int binCount = 0; ; ++binCount) {
                    if ((e = p.next) == null) {
                        p.next = newNode(hash, key, value, null);
                        if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                            treeifyBin(tab, hash);
                        break;
                    }
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        break;
                    p = e;
                }
            }
            if (e != null) { // existing mapping for key
                V oldValue = e.value;
                if (!onlyIfAbsent || oldValue == null)
                    e.value = value;
                //節點訪問後的鉤子操作,模板方法  linkedHashMap實現
                afterNodeAccess(e);
                return oldValue;
            }
        }
        ++modCount;
        if (++size > threshold)
            resize();
        //節點插入後的鉤子操作,也是模板方法  linkedHashMap實現
 
        afterNodeInsertion(evict);
        return null;
    }

看一下newNode()方法:

 // Create a regular (non-tree) node
    Node<K,V> newNode(int hash, K key, V value, Node<K,V> next) {
        //HashMap的實現是創建自己的Node節點
        return new Node<>(hash, key, value, next);
    }

但是我們發現LinkedHashMap重寫了這個方法:

 Node<K,V> newNode(int hash, K key, V value, Node<K,V> e) {
        //把新建的節點創建成自身的Entry類型的節點
        LinkedHashMap.Entry<K,V> p =
            new LinkedHashMap.Entry<K,V>(hash, key, value, e);
        //注意這個方法,這是排序的實現方法
        linkNodeLast(p);
        return p;
    }

現在我們知道HashMap那麼設計的原因:爲了兼容子類,這種模板方法的設計模式我們也可以借鑑

好,接下來看一下這個linkNodeLast方法:

// link at the end of list
//看頭上這行註釋我們就知道採用的是尾插法
    private void linkNodeLast(LinkedHashMap.Entry<K,V> p) {
        //將tail節點賦值給last節點,注意,第一次插入的時候顯然tail等於null
        LinkedHashMap.Entry<K,V> last = tail;
         //把新插入的節點p賦值給tail節點
        tail = p;
        //第一次插入時會走這個邏輯
        if (last == null)
            //把新插入的節點p賦值給head節點,如果是第一次插入,那麼此時head=tail=p
            head = p;
        else {
            //讓p的後退指針指向last節點,也就是一開始的tail尾節點
            p.before = last;
            //讓一開始的tail尾節點的前進指針指向p節點,至此就完成了新建的節點掛到了鏈表後面
            last.after = p;
        }
    }

接着看一下上面提到的另外兩個模版方法:

先看第一個:

void afterNodeAccess(Node<K,V> e) { // move node to last
        LinkedHashMap.Entry<K,V> last;
        //如果accessOrder爲true並且當前被訪問的節點不是尾節點
        //因此如果構造函數沒指定accssOrder爲true的話都不會走這個邏輯
        if (accessOrder && (last = tail) != e) {
            //把e強轉並賦值給p,然後把p的前一個節點賦值給b,p的下一個節點賦值給a
            LinkedHashMap.Entry<K,V> p =
                (LinkedHashMap.Entry<K,V>)e, b = p.before, a = p.after;
            //讓p的下一個節點置空
            p.after = null;
            //如果p的前一個節點等於空的話,證明他是頭節點,要將p移到尾節點的話,只要將p的後一個節 
            //點設置爲頭節點,這樣p就在鏈表之外了
            if (b == null)
                head = a;
            else
                //如果p的前一個節點不爲空,那簡單,只要將p前一個節點的前進指針指向p的下一個節點a
                //這樣就跳過了p
                b.after = a;
            //如果p的後一個節點不爲空,就將p的後一個節點的後退指針指向p的前一個節點,因爲是個雙向 
           //鏈表,所以要這麼做
            if (a != null)
                a.before = b;
            //這裏走的是p的後一個節點爲空的情況,將p的前一個節點賦值給last
            else
                last = b;
            //這裏只有一種情況:p後置節點爲空且p的前置節點爲空也就是說只有一個節點
            if (last == null)
                //把p設置爲head節點
                head = p;
            else {
                //這裏就是將p放到鏈表的最後,和last節點起個關聯關係
                p.before = last;
                last.after = p;
            }
            //尾節點設置成當前p節點
            tail = p;
            ++modCount;
        }
    }

我們看到如果插入元素過程沒有覆蓋已有元素,或者accessOrder沒有設置爲true,就不會觸發這個排序。

來看下一個方法:

//這個方法對應元素插入之後但是沒有覆蓋原有值的操作
void afterNodeInsertion(boolean evict) { // possibly remove eldest
        LinkedHashMap.Entry<K,V> first;
        //這個方法永遠不可能執行,因爲removeEldestEntry這個方法永遠返回false,
        //它會留給子類去實現邏輯,推測可以作爲最老的節點刪除 做一個LRU Cache,可以參考mabatis的 
     //LRUCache實現
        if (evict && (first = head) != null && removeEldestEntry(first)) {
            K key = first.key;
            removeNode(hash(key), key, null, false, true);
        }
    }

四、get方法相關

這個方法比較簡單 ,不做過多解釋了

public V get(Object key) {
        Node<K,V> e;
        //調用的是hashmap的getNode方法,不再做解釋
        if ((e = getNode(hash(key), key)) == null)
            return null;
        //多了一個訪問控制的判斷,如果爲true則每次把被訪問的元素放到鏈表尾
        if (accessOrder)
            afterNodeAccess(e);
        return e.value;
    }

 

五、如何迭代

首先我們知道HashMap的迭代是 HashMap.entrySet.iterator方法來獲取迭代器,我們看源碼不難發現,LinkedhashMap重寫了entrySet方法:

public Set<Map.Entry<K,V>> entrySet() {
        Set<Map.Entry<K,V>> es;
        //new了一個LinkedentrySet對象
        return (es = entrySet) == null ? (entrySet = new LinkedEntrySet()) : es;
    }

來看一下LinkedEntrySet的代碼:

//我們先看這一小段就夠了,這個set是LinkedHashMap的內部類,他實現了iterator方法
final class LinkedEntrySet extends AbstractSet<Map.Entry<K,V>> {
        public final int size()                 { return size; }
        public final void clear()               { LinkedHashMap.this.clear(); }
        public final Iterator<Map.Entry<K,V>> iterator() {
            //返回的是LinkedEntryIterator這個迭代器,這也是定義在內部的
            return new LinkedEntryIterator();
        }
        public final boolean contains(Object o) {
            if (!(o instanceof Map.Entry))
                return false;
            Map.Entry<?,?> e = (Map.Entry<?,?>) o;
            Object key = e.getKey();
            Node<K,V> candidate = getNode(hash(key), key);
            return candidate != null && candidate.equals(e);
        }
        

來看一下上面這個迭代器:

//來看一下這個迭代器,他的子類主要是
 abstract class LinkedHashIterator {
         //下一個節點
        LinkedHashMap.Entry<K,V> next;
         //當前節點
        LinkedHashMap.Entry<K,V> current;
         //期待的修改次數,這個和上一篇ArrayList的迭代器同理,不做過多介紹
        int expectedModCount;

        LinkedHashIterator() {
            //首先將 head表頭節點作爲下一個節點
            next = head;
            //給定期待的修改次數
            expectedModCount = modCount;
            //當前節點爲null
            current = null;
        }

        public final boolean hasNext() {
            //如果下一個節點不爲空
            return next != null;
        }
        
         //這個方法是next方法的實現,來看一下
        final LinkedHashMap.Entry<K,V> nextNode() {
            //把next節點保存到e,第一次進來時next節點是表頭節點
            LinkedHashMap.Entry<K,V> e = next;
            //併發修改校驗
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
            //如果下一個元素是null,拋出異常
            if (e == null)
                throw new NoSuchElementException();
            //把e節點設置爲當前節點
            current = e;
            //next節點通過前進指針指向e的下一個節點
            next = e.after;
            return e;
        }

        public final void remove() {
            Node<K,V> p = current;
            if (p == null)
                throw new IllegalStateException();
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
            current = null;
            K key = p.key;
            removeNode(hash(key), key, null, false, false);
            expectedModCount = modCount;
        }
    }
//他繼承了上面這個迭代器並且實現了標準迭代器接口
final class LinkedEntryIterator extends LinkedHashIterator
        implements Iterator<Map.Entry<K,V>> {
        //來看一下next()方法的實現,他其實調用了父類的nextNode方法
        public final Map.Entry<K,V> next() { return nextNode(); }
    }

以上就是迭代的過程

寫在最後

      今天暫時先寫到這裏 ,因爲我要睡覺了,有什麼問題可以給我留言,不過相信看到這裏,你對linkedHashMap的排序實現原理也有了一些自己的理解。如果覺得對自己有幫助的,可以給我點個贊或者關注我,以後會盡量日更。原創不易,轉載請註明出處。

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