[JDK1.8] JAVA集合 LinkedHashMap源碼淺析

源碼和註釋來自JDK api 文檔

一 簡述

Map 接口的哈希表和鏈接列表實現,具有可預知的迭代順序。此實現與 HashMap 的不同之處在於,後者維護着一個運行於所有條目的雙重鏈接列表。此鏈接列表定義了迭代順序,該迭代順序通常就是將鍵插入到映射中的順序(插入順序)。注意,如果在映射中重新插入 鍵,則插入順序不受影響。(如果在調用 m.put(k, v) 前 m.containsKey(k) 返回了 true,則調用時會將鍵 k 重新插入到映射 m 中。)

此實現可以讓客戶避免未指定的、由 HashMap(及 Hashtable)所提供的通常爲雜亂無章的排序工作,同時無需增加與 TreeMap 相關的成本。使用它可以生成一個與原來順序相同的映射副本,而與原映射的實現無關:

     void foo(Map m) {
         Map copy = new LinkedHashMap(m);
         ...
     }

如果模塊通過輸入得到一個映射,複製這個映射,然後返回由此副本確定其順序的結果,這種情況下這項技術特別有用。(客戶通常期望返回的內容與其出現的順序相同。)

提供特殊的構造方法來創建鏈接哈希映射,該哈希映射的迭代順序就是最後訪問其條目的順序,從近期訪問最少到近期訪問最多的順序(訪問順序)。這種映射很適合構建 LRU 緩存。調用 put 或 get 方法將會訪問相應的條目(假定調用完成後它還存在)。putAll 方法以指定映射的條目集迭代器提供的鍵-值映射關係的順序,爲指定映射的每個映射關係生成一個條目訪問。任何其他方法均不生成條目訪問。特別是,collection 視圖上的操作不 影響底層映射的迭代順序。

可以重寫 removeEldestEntry(Map.Entry) 方法來實施策略,以便在將新映射關係添加到映射時自動移除舊的映射關係。

此類提供所有可選的 Map 操作,並且允許 null 元素。與 HashMap 一樣,它可以爲基本操作(add、contains 和 remove)提供穩定的性能,假定哈希函數將元素正確分佈到桶中。由於增加了維護鏈接列表的開支,其性能很可能比 HashMap 稍遜一籌,不過這一點例外:LinkedHashMap 的 collection 視圖迭代所需時間與映射的大小 成比例。HashMap 迭代時間很可能開支較大,因爲它所需要的時間與其容量 成比例。

鏈接的哈希映射具有兩個影響其性能的參數:初始容量和加載因子。它們的定義與 HashMap 極其相似。要注意,爲初始容量選擇非常高的值對此類的影響比對 HashMap 要小,因爲此類的迭代時間不受容量的影響。

注意,此實現不是同步的。如果多個線程同時訪問鏈接的哈希映射,而其中至少一個線程從結構上修改了該映射,則它必須 保持外部同步。這一般通過對自然封裝該映射的對象進行同步操作來完成。如果不存在這樣的對象,則應該使用 Collections.synchronizedMap 方法來“包裝”該映射。最好在創建時完成這一操作,以防止對映射的意外的非同步訪問:

Map m = Collections.synchronizedMap(new LinkedHashMap(...));

結構修改是指添加或刪除一個或多個映射關係,或者在按訪問順序鏈接的哈希映射中影響迭代順序的任何操作。在按插入順序鏈接的哈希映射中,僅更改與映射中已包含鍵關聯的值不是結構修改。在按訪問順序鏈接的哈希映射中,僅利用 get 查詢映射不是結構修改。)

Collection(由此類的所有 collection 視圖方法所返回)的 iterator 方法返回的迭代器都是快速失敗 的:在迭代器創建之後,如果從結構上對映射進行修改,除非通過迭代器自身的 remove 方法,其他任何時間任何方式的修改,迭代器都將拋出 ConcurrentModificationException。因此,面對併發的修改,迭代器很快就會完全失敗,而不冒將來不確定的時間任意發生不確定行爲的風險。

注意,迭代器的快速失敗行爲無法得到保證,因爲一般來說,不可能對是否出現不同步併發修改做出任何硬性保證。快速失敗迭代器會盡最大努力拋出 ConcurrentModificationException。因此,編寫依賴於此異常的程序的方式是錯誤的,正確做法是:迭代器的快速失敗行爲應該僅用於檢測程序錯誤。

二 架構圖

在這裏插入圖片描述
LinkedHashMap: 繼承了HashMap
主要字段

名稱 註釋
head 雙向鏈表的頭(最大)。
tail 雙向鏈表的尾部(最小)。
accessOrder 此鏈接哈希映射的迭代排序方法:對於訪問順序爲true,對於插入順序爲false。

LinkedHashMap.Entry 繼承了 Node
主要字段

名稱 註釋
before 前一個節點
after 後一個節點

三 構造方法

LinkedHashMap()

註釋: 使用默認初始容量(16)和加載因子(0.75)構造一個空的插入順序LinkedHashMap實例。

    public LinkedHashMap() {
        super();
        accessOrder = false;
    }

LinkedHashMap(Map)

註釋:使用與指定映射相同的映射構造一個插入有序的LinkedHashMap實例。 LinkedHashMap實例使用默認加載因子(0.75)和足以保存指定映射中的映射的初始容量創建。

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

LinkedHashMap(int, float, boolean)

註釋:使用指定的初始容量,加載因子和排序模式構造一個空的LinkedHashMap實例。

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

LinkedHashMap(int)

註釋:使用指定的初始容量和默認加載因子(0.75)構造一個空的插入排序的LinkedHashMap實例。

    public LinkedHashMap(int initialCapacity) {
        super(initialCapacity);
        accessOrder = false;
    }

LinkedHashMap(int, float)

註釋:使用指定的初始容量和加載因子構造一個空的插入排序的LinkedHashMap實例。

    public LinkedHashMap(int initialCapacity, float loadFactor) {
        super(initialCapacity, loadFactor);
        accessOrder = false;
    }

四 核心方法

添加 newNode(int, K, V, Node)

註釋:LinkedHashMap 調用 HashMap 的put方法,重寫了 newNode方法,使得添加的元素安裝雙鏈表的形式記錄

	// 這個方法在 HashMap中
   	public V put(K key, V value) {
        return putVal(hash(key), key, value, false, true);
    }
	final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {
		··· 省略
		
		tab[i] = newNode(hash, key, value, null);
		
		··· 省略
        ++modCount;
        if (++size > threshold)
            resize();
        afterNodeInsertion(evict);
	}
	
    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(Node)

註釋:鏈接在列表的末尾

    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;
        }
    }

afterNodeInsertion

註釋:這個方法實現移除掉最近最少使用的節點,需要重寫 removeEldestEntry

    void afterNodeInsertion(boolean evict) { // possibly remove eldest
        LinkedHashMap.Entry<K,V> first;
        if (evict && (first = head) != null && removeEldestEntry(first)) {
            K key = first.key;
            removeNode(hash(key), key, null, false, true);
        }
    }
	protected boolean removeEldestEntry(Map.Entry<K,V> eldest) {
        return false;
    }

獲取 get

註釋:

    public V get(Object key) {
        Node<K,V> e;
        if ((e = getNode(hash(key), key)) == null)
            return null;
        if (accessOrder)
            afterNodeAccess(e);
        return e.value;
    }

訪問排序 afterNodeAccess

註釋: 這個方法是在創建LinkedHashMap時 accessOrder 設置爲 true;調用這個方法將把訪問的這個節點移動到鏈表的尾部,LRU算法,LRU(least recently used)最近最少使用。

    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;
        }
    }

移除 afterNodeRemoval

註釋:這個方法在HashMap中,刪除後調用 afterNodeRemoval

    public V remove(Object key) {
        Node<K,V> e;
        return (e = removeNode(hash(key), key, null, false, true)) == null ?
            null : e.value;
    }
    final Node<K,V> removeNode(int hash, Object key, Object value,
                               boolean matchValue, boolean movable) {
	··· 省略
       if (node != null && (!matchValue || (v = node.value) == value ||
                           (value != null && value.equals(v)))) {
          if (node instanceof TreeNode)
              ((TreeNode<K,V>)node).removeTreeNode(this, tab, movable);
          else if (node == p)
              tab[index] = node.next;
          else
              p.next = node.next;
          ++modCount;
          --size;
          afterNodeRemoval(node);
          return node;
      }
	··· 省略
	}

afterNodeRemoval

註釋:LinkedHashMap 重寫了這個方法,斷開鏈表的指定節點

    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;
        if (a == null)
            tail = b;
        else
            a.before = b;
    }

移除最舊的節點 removeEldestEntry

源碼註釋

如果此映射應刪除其最舊條目,則返回true。在將新條目插入地圖後,put和putAll將調用此方法。它爲實現者提供了在每次添加新條目時刪除最舊條目的機會。如果映射表示緩存,這將非常有用:它允許映射通過刪除過時條目來減少內存消耗。

示例使用:此覆蓋將允許地圖增長到100個條目,然後在每次添加新條目時刪除最舊的條目,保持100個條目的穩定狀態。

     private static final int MAX_ENTRIES = 100;

     protected boolean removeEldestEntry(Map.Entry eldest){
        return size()> MAX_ENTRIES;
     }

此方法通常不會以任何方式修改映射,而是允許映射根據其返回值進行自我修改。此方法允許直接修改映射,但如果它這樣做,則必須返回false(表示映射不應嘗試進一步修改)。從此方法中修改映射後返回true的效果未指定。

此實現僅返回false(因此此映射的行爲類似於普通映射 - 永遠不會刪除最舊的元素)。

把LinkedHashMap 的方法重寫了,節點數量達到一定程度返回true就可以移除最舊的節點,

	protected boolean removeEldestEntry(Map.Entry<K,V> eldest) {
        return false;
    }

實例:
當容器的數量大於1024時,添加就會把head的節點移除掉

		Map<String, String> linkedHashMap = new LinkedHashMap<String, String>(16, .75f, true) {
			private static final long serialVersionUID = -3295770436308811037L;
			private static final int SIZE_MAX = 1024;
			@Override
			protected boolean removeEldestEntry(java.util.Map.Entry<String, String> eldest) {
				return size() > SIZE_MAX;
			}
		};
		
		for (int i = 0; i < 1200; i++) {
			linkedHashMap.put("" + i, "a_" + i);
		}
		
		System.out.println(linkedHashMap);

迭代器 Iterator

架構圖:
在這裏插入圖片描述

LinkedHashIterator

    // Iterators

    abstract class LinkedHashIterator {
        LinkedHashMap.Entry<K,V> next;		// 下一個節點的指針
        LinkedHashMap.Entry<K,V> current;	// 當前節點的指針
        int expectedModCount;
		// 構造方法, 初始化next節點
        LinkedHashIterator() {
            next = head;
            expectedModCount = modCount;
            current = null;
        }
		// 是否有下一個節點
        public final boolean hasNext() {
            return next != null;
        }
		// 獲取下一個節點
        final LinkedHashMap.Entry<K,V> nextNode() {
            LinkedHashMap.Entry<K,V> e = next;
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
            if (e == null)
                throw new NoSuchElementException();
            current = e;
            next = e.after;
            return e;
        }
		// 移除最近一次調用nextNode 返回的節點
        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;
        }
    }


LinkedKeyIterator

LinkedValueIterator

LinkedEntryIterator

    final class LinkedKeyIterator extends LinkedHashIterator
        implements Iterator<K> {
        public final K next() { return nextNode().getKey(); }
    }

    final class LinkedValueIterator extends LinkedHashIterator
        implements Iterator<V> {
        public final V next() { return nextNode().value; }
    }

    final class LinkedEntryIterator extends LinkedHashIterator
        implements Iterator<Map.Entry<K,V>> {
        public final Map.Entry<K,V> next() { return nextNode(); }
    }
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章