文章目錄
源碼和註釋來自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(); }
}