LinkedHashMap繼承了HashMap,他在HashMap的基礎上增加了一個雙向鏈表的結構,鏈表預設維持key插入的順序,重複的key值插入不會改變順序,適用於使用者需要返回一個順序相同的map對象的情況。還可以產生access-order順序的版本,按照最近訪問順序來儲存,剛被訪問的結點處於鏈表的末尾,適合LRU,put get compute merge都算作一次訪問,其中put key值相同的結點也算作一次訪問,replace只有在換掉一個索引值對的時候才算一次訪問,putAll產生的訪問順序取決於原本map的迭代器實現。
在插入索引值對時,可以通過對removeEldestEntry重寫來實現新索引值對插入時自動刪除最舊的索引值對
擁有HashMap提供的方法,迭代器因為是通過遍曆雙向鏈表,所以額外開銷與size成正比與capacity無關,因此選擇過大的初始大小對於遍曆時間的增加沒有HashMap嚴重,後者的遍曆時間依賴與capacity。
同樣是非安全執行緒方法,對於LinkedHashMap來說,修改結構的操作除了增加和刪除索引值對外,還有對於access-order時進行了access導致迭代器順序改變,主要是get操作,對於插入順序的來說,僅僅修改一個已有key值的value值不是一個修改結構的操作,但對於訪問順序,put和get已有的key值會改變順序。迭代器也是fail-fast設計,但是fail-fast只是一個調試功能,一個設計良好的程式不應該出現這個錯誤
因為HashMap加入了TreeNode,所以現在LinkedHashMap也有這個功能
以下描述中的鏈表,若無特別說明都是指LinkedHashMap的雙向鏈表
先來看一下基本結構,每個索引值對加入了前後指標,集合加入了頭尾指標來形成雙向鏈表,accessOrder代錶鏈表是以訪問順序還是插入順序儲存
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); } } /** * The head (eldest) of the doubly linked list.頭部 */ transient LinkedHashMap.Entry<K,V> head; /** * The tail (youngest) of the doubly linked list.尾部 */ transient LinkedHashMap.Entry<K,V> tail; //true訪問順序 false插入順序 final boolean accessOrder;
然後是幾個內部方法。linkNodeLast將p串連到鏈表尾部
private void linkNodeLast(LinkedHashMap.Entry<K,V> p) { LinkedHashMap.Entry<K,V> last = tail; tail = p; if (last == null) head = p;//原本鏈表為空白則p同時為頭部 else { p.before = last; last.after = p; } }
transferLinks用dst替換src
private void transferLinks(LinkedHashMap.Entry<K,V> src, LinkedHashMap.Entry<K,V> dst) { LinkedHashMap.Entry<K,V> b = dst.before = src.before; LinkedHashMap.Entry<K,V> a = dst.after = src.after; if (b == null) head = dst; else b.after = dst; if (a == null) tail = dst; else a.before = dst; }
reinitialize在調用HashMap方法的基礎上,將head和tail設為null
void reinitialize() { super.reinitialize(); head = tail = null; }
newNode產生一個LinkedHashMap結點,next指向e,插入到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);//建立一個索引值對,next指向e linkNodeLast(p);//p插入到LinkedHashMap鏈表末端 return p; }
replacementNode根據原結點產生一個LinkedHashMap結點替換原結點
Node<K,V> replacementNode(Node<K,V> p, Node<K,V> next) { LinkedHashMap.Entry<K,V> q = (LinkedHashMap.Entry<K,V>)p; LinkedHashMap.Entry<K,V> t = new LinkedHashMap.Entry<K,V>(q.hash, q.key, q.value, next);//產生一個新的索引值對next是給出的next參數 transferLinks(q, t);//用t替換q return t; }
newTreeNode產生一個TreeNode結點,next指向next,插入到LinkedHashMap鏈表末端
TreeNode<K,V> newTreeNode(int hash, K key, V value, Node<K,V> next) { TreeNode<K,V> p = new TreeNode<K,V>(hash, key, value, next);//產生一個TreeNode,next指向參數next linkNodeLast(p);//p插入到LinkedHashMap鏈表末端 return p; }
replacementTreeNode根據結點p產生一個新的TreeNode,next設為給定的next,替換原本的p
TreeNode<K,V> replacementTreeNode(Node<K,V> p, Node<K,V> next) { LinkedHashMap.Entry<K,V> q = (LinkedHashMap.Entry<K,V>)p; TreeNode<K,V> t = new TreeNode<K,V>(q.hash, q.key, q.value, next); transferLinks(q, t);//根據結點p產生一個新的TreeNode,next設為給定的next,替換原本的p return t; }
afterNodeRemoval從LinkedHashMap的鏈上移除結點e
void afterNodeRemoval(Node<K,V> e) { 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; }
afterNodeInsertion可能移除最舊的結點,需要evict為true同時鏈表不為空白同時removeEldestEntry需要重寫
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在訪問過後將結點e移動到鏈表尾部,需要Map是access-order,若移動成功則增加modCount
void afterNodeAccess(Node<K,V> e) { LinkedHashMap.Entry<K,V> last; if (accessOrder && (last = tail) != e) {//Map是access-order同時e不是鏈表的尾部 LinkedHashMap.Entry<K,V> p = (LinkedHashMap.Entry<K,V>)e, b = p.before, a = p.after; p.after = null; if (b == null)//將結點e從鏈表中剪下 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;//結點e移動到鏈表尾部 ++modCount;//因為有access-order下結點被移動,所以增加modCount } }
建構函式方面,accessOrder預設是false插入順序,初始大小為16,負載因子為0.75,這裡是同HashMap。複製構造也是調用了HashMap.putMapEntries方法
containsValue遍曆鏈表尋找相等的value值,這個操作一定不會造成結構改變
public boolean containsValue(Object value) { for (LinkedHashMap.Entry<K,V> e = head; e != null; e = e.after) {//檢查同樣是根據LinkedHashMap提供的鏈表順序進行遍曆 V v = e.value; if (v == value || (value != null && value.equals(v))) return true; } return false; }
get方法複用HashMap的getNode方法,若找到結點且Map是訪問順序時,要將訪問的結點放到鏈表最後,若沒找到則返回null。而getOrDefault僅有的區別是沒找到時返回defaultValue
public V get(Object key) { Node<K,V> e; if ((e = getNode(hash(key), key)) == null)//複用HashMap的getNode方法 return null; if (accessOrder) afterNodeAccess(e);//access-order時將e放到隊尾 return e.value; } public V getOrDefault(Object key, V defaultValue) { Node<K,V> e; if ((e = getNode(hash(key), key)) == null) return defaultValue;//複用HashMap的getNode方法,若沒有找到對應的結點則返回defaultValue if (accessOrder) afterNodeAccess(e);//access-order時將e放到隊尾 return e.value; }
clear方法在HashMap的基礎上要把head和tail設為null
public void clear() { super.clear(); head = tail = null; }
removeEldestEntry在put和putAll插入索引值對時調用,原本是一定返回false的,如果要自動刪除最舊的索引值對要返回true,需要進行重寫。比如下面這個例子,控制size不能超過100
private static final int MAX_ENTRIES = 100; protected boolean removeEldestEntry(Map.Entry eldest) { return size() > MAX_ENTRIES; }
下面兩個方法和HashMap相似,返回key的Set和value的Collection還有返回索引值對的Set,這個是直接引用,所以對它們的remove之類的修改會直接反饋到LinkedHashMap上
public Set<K> keySet() { Set<K> ks = keySet; if (ks == null) { ks = new LinkedKeySet(); keySet = ks; } return ks;//返回key值的set } public Collection<V> values() { Collection<V> vs = values; if (vs == null) { vs = new LinkedValues(); values = vs; } return vs;//返回一個包含所有value值的Collection } public Set<Map.Entry<K,V>> entrySet() { Set<Map.Entry<K,V>> es; return (es = entrySet) == null ? (entrySet = new LinkedEntrySet()) : es;//返回一個含有所有索引值對的Set }
檢查HashMap的putVal方法,我們可以看到在找到了相同key值並修改value值時會調用afterNodeAccess,對於access-order會改變結點順序
if (e != null) { // 找到了相同的key則修改value值並返回舊的value V oldValue = e.value; if (!onlyIfAbsent || oldValue == null) e.value = value; afterNodeAccess(e); return oldValue; }