LinkedHashMap類似於HashMap,但是迭代遍歷它時,取得“鍵值對”的順序是插入次序,或者是最近最少使用(LRU)的次序。只比HashMap慢一點;而在迭代訪問時反而更快,因為它使用鏈表維護內部次序(HashMap是基於散列表實現的,相關HashMap的內容可以看《Java集合類》和《HashMap源碼分析》)。
1 public class LinkedHashMap<K,V> extends HashMap<K,V> implements Map<K,V>
LinkedHashMap繼承自HashMap並實現了Map接口。
LinkedHashMap只定義了兩個屬性:
1 /** 2 * The head of the doubly linked list. 3 * 雙向鏈表的頭節點 4 */ 5 private transient Entry<K,V> header; 6 /** 7 * The iteration ordering method for this linked hash map: true 8 * for access-order, false for insertion-order. 9 * true表示最近最少使用次序,false表示插入順序 10 */ 11 private final boolean accessOrder;
LinkedList一共提供了五個構造方法:
1 // 構造方法1,構造一個指定初始容量和負載因子的、按照插入順序的LinkedList 2 public LinkedHashMap(int initialCapacity, float loadFactor) { 3 super(initialCapacity, loadFactor); 4 accessOrder = false; 5 } 6 // 構造方法2,構造一個指定初始容量的LinkedHashMap,取得鍵值對的順序是插入順序 7 public LinkedHashMap(int initialCapacity) { 8 super(initialCapacity); 9 accessOrder = false; 10 } 11 // 構造方法3,用默認的初始化容量和負載因子創建一個LinkedHashMap,取得鍵值對的順序是插入順序 12 public LinkedHashMap() { 13 super(); 14 accessOrder = false; 15 } 16 // 構造方法4,通過傳入的map創建一個LinkedHashMap,容量為默認容量(16)和(map.zise()/DEFAULT_LOAD_FACTORY)+1的較大者,裝載因子為默認值 17 public LinkedHashMap(Map<? extends K, ? extends V> m) { 18 super(m); 19 accessOrder = false; 20 } 21 // 構造方法5,根據指定容量、裝載因子和鍵值對保持順序創建一個LinkedHashMap 22 public LinkedHashMap(int initialCapacity, 23 float loadFactor, 24 boolean accessOrder) { 25 super(initialCapacity, loadFactor); 26 this.accessOrder = accessOrder; 27 }
從構造方法中可以看出,默認都采用插入順序來維持取出鍵值對的次序。所有構造方法都是通過調用父類的構造方法來創建對象的。
LinkedHashMap是基於雙向鏈表的,而且屬性中定了一個header節點,為什么構造方法都沒有對其進行初始化呢?
注意LinkedHashMap中有一個init()方法, HashMap的構造方法都調用了init()方法,這里LinkedHashMap的構造方法在調用父類構造方法后將從父類構造方法中調用init()方法(這也解釋了為什么HashMap中會有一個沒有內容的init()方法)。
1 void init() { 2 header = new Entry<K,V>(-1, null, null, null); 3 header.before = header.after = header; 4 }
看init()方法,的確是對header進行了初始化,並構造成一個雙向循環鏈表(和LinkedList的存儲結構是一樣的)。
transfer(HashMap.Entry[] newTable)方法和init()方法一樣也在HashTable中被調用。transfer(HashMap.Entry[] newTable)方法在HashMap調用resize(int newCapacity)方法的時候被調用。
1 void transfer(HashMap.Entry[] newTable) { 2 int newCapacity = newTable.length; 3 for (Entry<K,V> e = header.after; e != header; e = e.after) { 4 int index = indexFor(e.hash, newCapacity); 5 e.next = newTable[index]; 6 newTable[index] = e; 7 } 8 }
根據鏈表節點e的哈希值計算e在新容量的table數組中的索引,並將e插入到計算出的索引所引用的鏈表中。
containsValue(Object value)
1 public boolean containsValue(Object value) { 2 // Overridden to take advantage of faster iterator 3 if (value==null) { 4 for (Entry e = header.after; e != header; e = e.after) 5 if (e.value==null) 6 return true; 7 } else { 8 for (Entry e = header.after; e != header; e = e.after) 9 if (value.equals(e.value)) 10 return true; 11 } 12 return false; 13 }
重寫父類的containsValue(Object value)方法,直接通過header遍歷鏈表判斷是否有值和value相等,而不用查詢table數組。
get(Object key)
1 public V get(Object key) { 2 Entry<K,V> e = (Entry<K,V>)getEntry(key); 3 if (e == null) 4 return null; 5 e.recordAccess(this); 6 return e.value; 7 }
get(Object key)方法通過HashMap的getEntry(Object key)方法獲取節點,並返回該節點的value值,獲取節點如果為null則返回null。recordAccess(HashMap<K,V> m)是LinkedHashMap的內部類Entry的一個方法,將在介紹Entry的時候進行詳細的介紹。
clear()
1 public void clear() { 2 super.clear(); 3 header.before = header.after = header; 4 }
clear()方法先調用父類的方法clear()方法,之后將鏈表的header節點的before和after引用都指向header自身,即header節點就是一個雙向循環鏈表。這樣就無法訪問到原鏈表中剩余的其他節點,他們都將被GC回收。
以上的內容多多少少都涉及到了LinkedHashMap的內部類Entry<K,V>,下面詳細介紹這個內部類。
1 // 這是一個私有的、靜態的內部類,繼承自HashMap的Entry。 2 private static class Entry<K,V> extends HashMap.Entry<K,V> { 3 // 對前后節點的引用 4 Entry<K,V> before, after; 5 // 構造方法直接調用父類的構造方法 6 Entry(int hash, K key, V value, HashMap.Entry<K,V> next) { 7 super(hash, key, value, next); 8 } 9 10 // 移除該節點,只需修改前一節點的after引用和后一節點的before引用 11 private void remove() { 12 // 修改后該節點服務再被訪問,會被GC回收 13 before.after = after; 14 after.before = before; 15 } 16 17 // 在指定節點之前插入當前節點(雙向鏈表插入節點的過程) 18 private void addBefore(Entry<K,V> existingEntry) { 19 // 將當前節點的after引用指向existingEntry 20 after = existingEntry; 21 // 將before的引用指向existingEntry節點的前一節點 22 before = existingEntry.before; 23 // 將原先existingEntry節點的前一節點的after引用指向當前節點 24 before.after = this; 25 // 將原先existingEntry節點的后一節點的before引用指向當前節點 26 after.before = this; 27 } 28 29 // 當調用此類的get方法或put方法(put方法將調用到父類HashMap.Entry的put 30 // 方法)都將調用到recordAccess(HashMap<K,V> m)方法 31 // 如果accessOrder為true,即使用的是最近最少使用的次序,則將當前被修改的 32 // 節點移動到header節點之前,即鏈表的尾部。 33 // 這也是為什么在HashMap.Entry中有一個空的 34 // recordAccess(HashMap<K,V> m)方法的原因 35 void recordAccess(HashMap<K,V> m) { 36 LinkedHashMap<K,V> lm = (LinkedHashMap<K,V>)m; 37 if (lm.accessOrder) { 38 lm.modCount++; 39 remove(); 40 addBefore(lm.header); 41 } 42 } 43 // 和recordAccess(HashMap<K.V> m)方法一樣,在HashMap.Entry中同樣有一個對應的空方法。當進行刪除(remove)操作的時候會被調用 44 void recordRemoval(HashMap<K,V> m) { 45 remove(); 46 } 47 }
介紹完了內部類Entry,下面是創建一個Entry節點和添加一個Entry的兩個方法。
createEntry(int hash,K key,V value,int bucketIndex)
1 void createEntry(int hash, K key, V value, int bucketIndex) { 2 HashMap.Entry<K,V> old = table[bucketIndex]; 3 Entry<K,V> e = new Entry<K,V>(hash, key, value, old); 4 table[bucketIndex] = e; 5 e.addBefore(header); 6 size++; 7 }
createEntry(int hash,K key,V value,int bucketIndex)方法覆蓋了父類HashMap中的方法。這個方法不會拓展table數組的大小。該方法首先保留table中bucketIndex處的節點,然后調用Entry的構造方法(將調用到父類HashMap.Entry的構造方法)添加一個節點,即將當前節點的next引用指向table[bucketIndex] 的節點,之后調用的e.addBefore(header)是修改鏈表,將e節點添加到header節點之前。
該方法同時在table[bucketIndex]的鏈表中添加了節點,也在LinkedHashMap自身的鏈表中添加了節點。
addEntry(int hash, K key, V value, int bucketIndex)
1 void addEntry(int hash, K key, V value, int bucketIndex) { 2 createEntry(hash, key, value, bucketIndex); 3 Entry<K,V> eldest = header.after; 4 if (removeEldestEntry(eldest)) { 5 removeEntryForKey(eldest.key); 6 } else { 7 if (size >= threshold) 8 resize(2 * table.length); 9 } 10 }
首先調用createEntry(int hash,K key,V value,int bucketIndex)方法,之后獲取LinkedHashMap中“最老”(最近最少使用)的節點,接着涉及到了removeEldestEntry(Entry<K,V> eldest)方法,來看一下:
1 protected boolean removeEldestEntry(Map.Entry<K,V> eldest) { 2 return false; 3 }
為什么這個方法始終返回false?
結合上面的addEntry(int hash,K key,V value,int bucketIndex)方法,這樣設計可以使LinkedHashMap成為一個正常的Map,不會去移除“最老”的節點。
為什么不在代碼中直接去除這部分邏輯而是設計成這樣呢?
這為開發者提供了方便,若希望將Map當做Cache來使用,並且限制大小,只需繼承LinkedHashMap並重寫removeEldestEntry(Entry<K,V> eldest)方法,像這樣:
1 private static final int MAX_ENTRIES = 100; 2 protected boolean removeEldestEntry(Map.Entry eldest) { 3 return size() > MAX_ENTRIES; 4 }
LinkedHashMap除了以上內容外還有和迭代相關的三個方法及三個內部類以及一個抽象內部類,分別是:newKeyIterator()、newValueIterator()、newEntryIterator()和KeyIterator類、ValueIterator類、EntryIterator類以及LinkedHashIterator類。
三個new方法分別返回對應的三個類的實例。而三個類都繼承自抽象類LinkedHashIterator。下面看迭代相關的三個類。
1 private class KeyIterator extends LinkedHashIterator<K> { 2 public K next() { return nextEntry().getKey(); } 3 } 4 private class ValueIterator extends LinkedHashIterator<V> { 5 public V next() { return nextEntry().value; } 6 } 7 private class EntryIterator extends LinkedHashIterator<Map.Entry<K,V>> { 8 public Map.Entry<K,V> next() { return nextEntry(); } 9 }
從上面可以看出這三個類都很簡單,只有一個next()方法,next()方法也只是去調用LinkedHashIterator類中相應的方法。 和KeyIterator類、ValueIterator類、EntryIterator類以及LinkedHashIterator類。
下面是LinkedHashIterator類的內容。
1 private abstract class LinkedHashIterator<T> implements Iterator<T> { 2 Entry<K,V> nextEntry = header.after; 3 Entry<K,V> lastReturned = null; 4 // 和LinkedList中ListItr類定義了expectedModCount用途一致 5 int expectedModCount = modCount; 6 // 下一個節點如果是header節點說明當前節點是鏈表的最后一個節點,即已經遍歷完鏈表了,沒有下一個節點了 7 public boolean hasNext() { 8 return nextEntry != header; 9 } 10 //移除上一次被返回的節點lastReturned 11 public void remove() { 12 if (lastReturned == null) 13 throw new IllegalStateException(); 14 if (modCount != expectedModCount) 15 throw new ConcurrentModificationException(); 16 LinkedHashMap.this.remove(lastReturned.key); 17 lastReturned = null; 18 expectedModCount = modCount; 19 } 20 // 返回下一個節點 21 Entry<K,V> nextEntry() { 22 if (modCount != expectedModCount) 23 throw new ConcurrentModificationException(); 24 if (nextEntry == header) 25 throw new NoSuchElementException(); 26 // 獲取並記錄返回的節點 27 Entry<K,V> e = lastReturned = nextEntry; 28 // 保存對下一個節點的引用 29 nextEntry = e.after; 30 return e; 31 } 32 }
LinkedHashMap本應和HashMap及LinkedList一起分析,比較他們的異同。為了彌補,這里簡單的總結一些他們之間的異同:
HashMap使用哈希表來存儲數據,並用拉鏈法來處理沖突。LinkedHashMap繼承自HashMap,同時自身有一個鏈表,使用鏈表存儲數據,不存在沖突。LinkedList和LinkedHashMap一樣使用一個雙向循環鏈表,但存儲的是簡單的數據,並不是“鍵值對”。所以HashMap和LinkedHashMap是Map,而LinkedList是一個List,這是他們本質的區別。LinkedList和LinkedHashMap都可以維護內容的順序,但HashMap不維護順序。
附上HashMap和LinkedList的源碼分析,便於對比:
HashMap:http://www.cnblogs.com/hzmark/archive/2012/12/24/HashMap.html
LinkedList:http://www.cnblogs.com/hzmark/archive/2012/12/25/LinkedList.html