LinkedHashMap源碼解析

1、LinkedHashMap類的說明

1.1 解釋說明

/**
 * 哈希表和鏈表實現了Map接口,具有可預測的迭代順序(也就是說LinkedHashMap是有序的)
 * 這個工具不同於HashMap因爲它在entry節點上多維護了一組雙向鏈表,用來保證entry
 * 插入有序。(我們知道在HashMap中每一個桶中的節點用一個單鏈表或者紅黑樹維護)
 * 這個雙向鏈表保證了entry的迭代順序(也就是保證entry有序),通常是鍵插入的順序。
 * 但是要注意,如果一個鍵是重新插入並不會影響它的順序(也就是說如果鍵存在,那麼
 * 再插入一個相同的Key的時候,它首先會執行m.containsKey(),如果存在則返回true)
 *
 *
 * 這個實現使它的客戶端避免了由HashMap和HashTable所提供的未指定的、通常是混亂
 * 的排序,並且不會增加類似於TreeMap的成本(這裏不是很理解treeMap哪裏成本比較高
 * 等以後看了treeMap再回來看看)。它還可以生成與原始Map相同的副本,而不管原始Map
 * 的實現方式。
 * <pre>
 *     void foo(Map m) {
 *         Map copy = new LinkedHashMap(m);
 *         ...
 *     }
 * </pre>
 * 
 * 
 * 如果一個模塊接收輸入,複製它,然後又副本決定其返回的順序。那麼這個工具會非常有用。
 * 客戶端通常喜歡按照他們提交的順序顯示結果。
 * 
 * 
 * LinkedHashMap(int,float,boolean) 這個構造函數一共一個創建鏈式HashMap,它的迭代順序
 * 是它的entry最後一次被訪問。從最近最少訪問到最近最多訪問。這種方式的映射特別適用於
 * LRU緩存(最近最少使用),調用{@code put}、{@code putIfAbsent}、{@code get}、{@code getOrDefault}、
 * {@code compute}、{@code computeIfAbsent}、{@code computeIfPresent}或{@code merge}
 * 方法會導致對相應entry的訪問(假設在調用完成後存在)。如果值被替換,{@code replace}方法
 * 只會導致對entry的訪問。{@code putAll}方法爲指定映射中的每個映射生成一個entry訪問,其順序
 * 是鍵值映射由指定映射的entry集迭代器提供。沒有其他方法生成entry訪問。,尤其是對集合
 * 視圖的操作而不是會影響支持映射的迭代順序。
 * 
 * 
 * 可以通過重寫removeEldestEntry(Map.Entry)方法,以便在向映射添加映射時強制執行自動刪除
 * 陳舊的映射。(一般用鏈表的形式進行存儲數據都需要有一個刪除的過程,不然內存空間會爆滿的)
 * 
 * 
 * 這個類提供了Map的所有操作(因爲它繼承了Hashmap且實現了Map接口),且允許空值。跟HashMap一樣,
 * 它提供固定的時間複雜度的性能對於基本的操作,像add(),contains()還有remove()。假設元素被均勻
 * 的分配到每個桶中,則LinkedHashMap的性能可能會比HashMap的性能稍微低一點,因爲要維護鏈表的開銷。
 * 但是有一個例外,LinkedHashMap迭代集合元素的時候,只跟集合中的元素個數有關,而於桶的多少無關。
 * 但是HashMap是跟桶的容量大小成正比的,因爲它要從第一個桶一個個找過去。
 * 
 * 
 * LinkedHashMap也有兩個屬性影響它的性能,初始容量和裝載因子。這個於HashMap一樣。但是要注意的是,
 * 如果把初始容量定的太高的話,對於LinedHashMap的影響比HashMap低,原因我們在前面已經說過了,那是
 * 因爲LinkedHashMap的迭代不受初始容量的影響。
 * 
 * 
 * 這個方法也是非線程安全的。如果有多個線程訪問,且至少有一個線程修改了Map,則必須外部自己加鎖。
 * 這通常是通過在一些自然封裝映射的對象上進行同步來實現的。
 * 
 * 
 * 如果不存在這樣的對象,則應該使用{@link Collections#synchronizedMap集合'包裝'映射。
 * synchronizedMap}方法。這最好在創建時完成,以防止意外地不同步地訪問映射:
 * Map m = Collections.synchronizedMap(new LinkedHashMap(...));
 * 
 * 
 * 一個結構性修改是指添加或者刪除一個或者多個映射的操作,又或者是在訪問hash鏈表的情況下
 * 影響了迭代的順序。在插入順序鏈接哈希映射中,僅更改與映射中已包含的鍵關聯的值不是結構
 * 性修改。在訪問順序的鏈接哈希映射中,僅用get()查詢映射是一種結構修改。
 * 
 * 
 * 跟HashMap一樣,迭代器也是採用"fail-fast"來對併發的結構性修改拋出異常,但是也不能用這個
 * 來保證併發性的修改,它只是檢測bug,最終還是要靠外部的同步來保證併發的修改安全。

1.2 數據結構

在這裏插入圖片描述
跟hashmap相比較主要多了一個雙向鏈表來維護entry,圖中的黑色雙向線。在JDK1.8中也是採用(數組+單鏈表+雙向鏈表+紅黑樹)

2、方法字段


    /**
     * 在繼承了HashMap的節點的基礎上,新增了雙向鏈表
     */
    static class Entry<K,V> extends HashMap.Node<K,V> {
        Entry<K,V> before, after;//雙向鏈表指針
        Entry(int hash, K key, V value, Node<K,V> next) {
            super(hash, key, value, next);
        }
    }
	//序列號
    private static final long serialVersionUID = 3801124242820219131L;

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

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

    /**
     *迭代的排序方法,如果是true,則按順序,
     *爲false,則不按順序
     * @serial
     */
    final boolean accessOrder;

從字段可以發現主要是繼承自hashMap然後增加了一個雙向鏈表和一個指向頭尾的指針,主要用來插入和刪除的方便。還有一個accessOrder主要用來指定要不要按照順序插入。

3、關鍵方法

linkNodeLast(LinkedHashMap.Entry<K,V> p)

    // 將節點鏈接到鏈表末尾
    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;
        }
    }

transferLinks(LinkedHashMap.Entry<K,V> src, LinkedHashMap.Entry<K,V> dst)

 // 其中的dst:destination目的節點,src:source源節點,這個函數的作用
	//就是用dst節點替換掉src節點,整個過程就是一個雙向鏈表的替換節點過程
    private void transferLinks(LinkedHashMap.Entry<K,V> src,
                               LinkedHashMap.Entry<K,V> dst) {
        LinkedHashMap.Entry<K,V> b = dst.before = src.before;//將dst的befor指針指向srcde before指針所指節點
        LinkedHashMap.Entry<K,V> a = dst.after = src.after;//將dst的after指針指向srcde after指針所指節點
        if (b == null)//如果前置節點不存在,則當前節點爲頭結點
            head = dst;
        else
            b.after = dst;
        if (a == null)//後置節點不存在,則爲尾結點
            tail = dst;
        else
            a.before = dst;
    }

這個方法是用來替換節點的,當映射到相同的key,想要替換掉對應的value就調用這個方法,可以看到這個是一個private方法,說明它是在內部調用,提供給我們的是其他方法比如replacementNode方法,主要的實現如圖所示。
在這裏插入圖片描述

afterNodeRemoval(Node<K,V> e)

//刪除節點
    void afterNodeRemoval(Node<K,V> e) { // unlink
        LinkedHashMap.Entry<K,V> p =
            (LinkedHashMap.Entry<K,V>)e, b = p.before, a = p.after;//標記被刪除節點的前後節點
        p.before = p.after = null;//節點的指針置空
        if (b == null)//沒有前置節點
            head = a;//則它的後置節點爲頭結點
        else
            b.after = a;//否則b的after節點鏈接到a
        if (a == null)//沒有後置節點
            tail = b;//b爲尾結點
        else
            a.before = b;//否則a的before節點鏈接到b
    }

afterNodeInsertion(boolean evict)

//插入節點,如果存在相同key,可能刪除第一個舊的節點
    void afterNodeInsertion(boolean evict) { 
        LinkedHashMap.Entry<K,V> first;
        if (evict && (first = head) != null && removeEldestEntry(first)) {//removeEldestEntry刪除舊節點,默認是false
            K key = first.key;
            removeNode(hash(key), key, null, false, true);
        }
    }

afterNodeAccess(Node<K,V> e)

	//訪問節點,則會將訪問的節點會被移到鏈表的最後位置,這個方法和afterNodeRemoval(),就可以實現LRU算法
    void afterNodeAccess(Node<K,V> e) { // move node to last
        LinkedHashMap.Entry<K,V> last;
		
        if (accessOrder && (last = tail) != e) {//判斷是否是按順序訪問,且不是訪問最後一個節點
            LinkedHashMap.Entry<K,V> p =
                (LinkedHashMap.Entry<K,V>)e, b = p.before, a = p.after;//下面的操作就是刪除該節點,然後將該節點鏈接到鏈表尾部,具體操作可以參考雙向鏈表的刪除節點和在末尾插入節點操作
            p.after = null;
            if (b == null)
                head = a;
            else
                b.after = a;
            if (a != null)
                a.before = b;
            else
                last = b;
            if (last == null)
                head = p;
            else {
                p.before = last;
                last.after = p;
            }
            tail = p;
            ++modCount;
        }
    }

4、總結

總的來說LinkedHashMap主要是繼承自HashMap,HashMap有的LinkedHashMap也都可以實現。最主要的區別在於LinkedHashMap多了雙向鏈表來維護節點的順序,使節點按插入的時間有序,此外還有一個就是實現了插入新節點的時候能夠判斷是否要刪除舊節點,可以用這個來實現LRU算法。

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