java 集合框架-LinkedHashMap
一、概述
1、繼承擴充套件HashMap,實現Map介面,基於雙向連結串列實現有序,支援插入有序和訪問順序
2、允許NULL元素,基本操作(add、contrains、remove)與HashMap一樣有穩定效能(hash分佈均勻情況下)
3、由於需要維護連結串列,效能較HashMap差,而迭代可能不一定,LinkedHashMap的迭代所需時間與 大小 成比例,HashMap迭代所需時間與 容量 成比例
4、初始容量和負載因子對效能有影響,這與HashMap相似,但初始容量非常大的影響比HashMap要小,因為迭代不受容量影響
5、非同步,非執行緒安全,可使用 Collections.synchronizedMap 封裝為同步
6、迭代同樣是 快速失敗 機制
二、原始碼分析
1、變數
/**
* The head of the doubly linked list.
*/
private transient Entry<K,V> header;
/**
* The iteration ordering method for this linked hash map: <tt>true</tt>
* for access-order, <tt>false</tt> for insertion-order.
*
* @serial
*/
private final boolean accessOrder;
header 保持連結串列頭的引用, accessOrder 表示改物件的有序性是訪問順序還是插入順序,預設為false:插入順序
2、建構函式
/**
* Constructs an empty insertion-ordered <tt>LinkedHashMap</tt> instance
* with the specified initial capacity and load factor.
*
* @param initialCapacity the initial capacity
* @param loadFactor the load factor
* @throws IllegalArgumentException if the initial capacity is negative
* or the load factor is nonpositive
*/
public LinkedHashMap(int initialCapacity, float loadFactor) {
super(initialCapacity, loadFactor);
accessOrder = false;
}
/**
* Constructs an empty insertion-ordered <tt>LinkedHashMap</tt> instance
* with the specified initial capacity and a default load factor (0.75).
*
* @param initialCapacity the initial capacity
* @throws IllegalArgumentException if the initial capacity is negative
*/
public LinkedHashMap(int initialCapacity) {
super(initialCapacity);
accessOrder = false;
}
/**
* Constructs an empty insertion-ordered <tt>LinkedHashMap</tt> instance
* with the default initial capacity (16) and load factor (0.75).
*/
public LinkedHashMap() {
super();
accessOrder = false;
}
/**
* Constructs an insertion-ordered <tt>LinkedHashMap</tt> instance with
* the same mappings as the specified map. The <tt>LinkedHashMap</tt>
* instance is created with a default load factor (0.75) and an initial
* capacity sufficient to hold the mappings in the specified map.
*
* @param m the map whose mappings are to be placed in this map
* @throws NullPointerException if the specified map is null
*/
public LinkedHashMap(Map<? extends K, ? extends V> m) {
super(m);
accessOrder = false;
}
/**
* Constructs an empty <tt>LinkedHashMap</tt> instance with the
* specified initial capacity, load factor and ordering mode.
*
* @param initialCapacity the initial capacity
* @param loadFactor the load factor
* @param accessOrder the ordering mode - <tt>true</tt> for
* access-order, <tt>false</tt> for insertion-order
* @throws IllegalArgumentException if the initial capacity is negative
* or the load factor is nonpositive
*/
public LinkedHashMap(int initialCapacity,
float loadFactor,
boolean accessOrder) {
super(initialCapacity, loadFactor);
this.accessOrder = accessOrder;
}
基本上與HashMap的建構函式對應上,每個建構函式都是super先構造父類,從建構函式看,只增加了一個 accessOrder 變數
3、核心實現
從建構函式看不到LinkedHashMap有啥不一樣,往下看發現它重寫實現了HashMap定義的許多鉤子方法,如 init():
@Override
void init() {
header = new Entry<>(-1, null, null, null);
header.before = header.after = header;
}
這個init是HashMap定義留給子類的鉤子,方便子類擴充套件,LinkedHashMap便是利用這些擴充套件點;初始化中,header 指向一個空的Entry,並且頭尾指標都是自身;這個初始化方法在HashMap的構造方法呼叫,能保證此時還沒有元素插入;後面的方法有很多關於這個雙向連結串列的指標操作,先看看這個連結串列Node的定義:
private static class Entry<K,V> extends HashMap.Entry<K,V> {
// These fields comprise the doubly linked list used for iteration.
Entry<K,V> before, after;
Entry(int hash, K key, V value, HashMap.Entry<K,V> next) {
super(hash, key, value, next);
}
/**
* Removes this entry from the linked list.
*/
private void remove() {
before.after = after;
after.before = before;
}
/**
* Inserts this entry before the specified existing entry in the list.
*/
private void addBefore(Entry<K,V> existingEntry) {
after = existingEntry;
before = existingEntry.before;
before.after = this;
after.before = this;
}
/**
* This method is invoked by the superclass whenever the value
* of a pre-existing entry is read by Map.get or modified by Map.set.
* If the enclosing Map is access-ordered, it moves the entry
* to the end of the list; otherwise, it does nothing.
*/
void recordAccess(HashMap<K,V> m) {
LinkedHashMap<K,V> lm = (LinkedHashMap<K,V>)m;
if (lm.accessOrder) {
lm.modCount++;
remove();
addBefore(lm.header);
}
}
void recordRemoval(HashMap<K,V> m) {
remove();
}
}
可以看到,LinkedHashMap的Entry也是繼承HashMap的Entry,並且增加了兩個變數:前後指標,重寫實現了HashMap定義的鉤子方法:recordAccess和recordRemoval,這兩個方法在HashMap.Entry定義但為空實現;recordAccess在已存在元素被get或者put的時候被呼叫,作用是維護元素的’訪問有序’,如果變數accessOrder為true(訪問順序),那麼該方法將當期訪問元素移動到連結串列最後(header的前面)以保持訪問順序,否則不做任何操作;而recordRemoval方法在元素被移除remove時呼叫,這裡呼叫了一個私有方法remove,實際是將刪除的元素從雙向連結串列移除掉;
接下來我們看看,一個元素正常put進來時的操作,LinkedHashMap並沒有重寫HashMap的put方法,而只是重寫了addEntry和createEntry方法:
/**
* This override alters behavior of superclass put method. It causes newly
* allocated entry to get inserted at the end of the linked list and
* removes the eldest entry if appropriate.
*/
void addEntry(int hash, K key, V value, int bucketIndex) {
super.addEntry(hash, key, value, bucketIndex);
// Remove eldest entry if instructed
Entry<K,V> eldest = header.after;
if (removeEldestEntry(eldest)) {
removeEntryForKey(eldest.key);
}
}
/**
* This override differs from addEntry in that it doesn't resize the
* table or remove the eldest entry.
*/
void createEntry(int hash, K key, V value, int bucketIndex) {
HashMap.Entry<K,V> old = table[bucketIndex];
Entry<K,V> e = new Entry<>(hash, key, value, old);
table[bucketIndex] = e;
e.addBefore(header);
size++;
}
回顧下HashMap的正常put流程(詳情可看之前文章):計算hash、獲取下標、查詢是否已存在key-value(如是則更新後結束)、新增Entry(addEntry)、判斷擴容、建立新Entry(createEntry);即 put 會呼叫addEntry,而addEntry會呼叫createEntry;LinkedHashMap 重寫的createEntry方法中,實現與HashMap只多了一句邏輯:e.addBefore(header),將當期新增的元素加到連結串列的最後一位,這裡就不需要判斷或者呼叫recordAccess,因為無論何種排序,新增都是排最後(最新位置);
而重寫的addEntry方法,呼叫父類的實現後,進行了一個判斷:是否刪除最舊的元素,這個判斷方法removeEldestEntry在LinkedHashMap中直接返回false,即不刪除最舊元素;那麼這個設計有什麼用了?我們知道LinkedHashMap支援維持訪問順序,也就是最近訪問的排序在前,在此排序方式下,最老的一個元素eldest也就是最舊沒有訪問過的,這就非常像LRU(Least recently used)快取了,加入新元素時,將最少使用的元素刪除掉;我們只需要簡單繼承下LinkedHashMap,設定accessorder 為true,重寫改方法:
private static final int MAX_ENTRIES = 100;
protected boolean removeEldestEntry(Map.Entry eldest) {
return size() > MAX_ENTRIES;
}
這樣就基於LinkedHashMap簡單實現了一個LRU快取;
而事實上真的是有這麼實現的,或者大家都已經使用過而不知道而已,在工程程式碼中通過Eclipse看這個方法的繼承實現:
可以看到mysql jdbc裡面的一個LRU快取就是這麼實現:
/**
* @author Mark Matthews
* @version $Id$
*/
public class LRUCache extends LinkedHashMap<Object, Object> {
private static final long serialVersionUID = 1L;
protected int maxElements;
public LRUCache(int maxSize) {
super(maxSize, 0.75F, true);
this.maxElements = maxSize;
}
/*
* (non-Javadoc)
*
* @see java.util.LinkedHashMap#removeEldestEntry(java.util.Map.Entry)
*/
@Override
protected boolean removeEldestEntry(Entry<Object, Object> eldest) {
return (size() > this.maxElements);
}
}
LinkedHashMap還重寫了HashMap兩個方法:transfer和containsValue,邏輯都是一樣的,只是利用了連結串列來迭代提高效能:
/**
* Transfers all entries to new table array. This method is called
* by superclass resize. It is overridden for performance, as it is
* faster to iterate using our linked list.
*/
@Override
void transfer(HashMap.Entry[] newTable, boolean rehash) {
int newCapacity = newTable.length;
for (Entry<K,V> e = header.after; e != header; e = e.after) {
if (rehash)
e.hash = (e.key == null) ? 0 : hash(e.key);
int index = indexFor(e.hash, newCapacity);
e.next = newTable[index];
newTable[index] = e;
}
}
/**
* Returns <tt>true</tt> if this map maps one or more keys to the
* specified value.
*
* @param value value whose presence in this map is to be tested
* @return <tt>true</tt> if this map maps one or more keys to the
* specified value
*/
public boolean containsValue(Object value) {
// Overridden to take advantage of faster iterator
if (value==null) {
for (Entry e = header.after; e != header; e = e.after)
if (e.value==null)
return true;
} else {
for (Entry e = header.after; e != header; e = e.after)
if (value.equals(e.value))
return true;
}
return false;
}
但是我覺得,不是說使用LinkedHashMap就效能比HashMap好,只能說這個迭地上效能稍微好一點,但是卻要增加頭尾指標的空間和維護雙向連結串列的操作;
最後來看看迭代器,應該可以猜到,肯定是基於雙向連結串列進行迭代而不再是底層table陣列
private abstract class LinkedHashIterator<T> implements Iterator<T> {
Entry<K,V> nextEntry = header.after;
Entry<K,V> lastReturned = null;
/**
* The modCount value that the iterator believes that the backing
* List should have. If this expectation is violated, the iterator
* has detected concurrent modification.
*/
int expectedModCount = modCount;
public boolean hasNext() {
return nextEntry != header;
}
public void remove() {
if (lastReturned == null)
throw new IllegalStateException();
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
LinkedHashMap.this.remove(lastReturned.key);
lastReturned = null;
expectedModCount = modCount;
}
Entry<K,V> nextEntry() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
if (nextEntry == header)
throw new NoSuchElementException();
Entry<K,V> e = lastReturned = nextEntry;
nextEntry = e.after;
return e;
}
}
其他迭代器與HashMap一樣,繼承父類LinkedHashIterator,重寫next方法返回需要的物件
private class KeyIterator extends LinkedHashIterator<K> {
public K next() { return nextEntry().getKey(); }
}
private class ValueIterator extends LinkedHashIterator<V> {
public V next() { return nextEntry().value; }
}
private class EntryIterator extends LinkedHashIterator<Map.Entry<K,V>> {
public Map.Entry<K,V> next() { return nextEntry(); }
}
三、總結
有幾個點是比較重要的:
1、雙向連結串列及維護,在Entry中增加了before、after指標
2、accessorder 控制順序方式:訪問順序、插入順序(預設)
3、可以繼承實現LRU快取
4、迭代方式使用連結串列,效能有提升,增加了連結串列空間和維護