JDK版本jdk1.8.0_191 1
從繼承體系的角度,LinkedHashMap是HashMap的子類
public class LinkedHashMap<K,V> extends HashMap<K,V> implements Map<K,V>
內部維護一個雙向鏈表,維護插入順序或者LRU順序。
/**
* 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;
還有一個字段爲accessOrder
表示的是鏈表維護的是插入順序
還是LRU順序
。
默認false表示維護插入順序,而true則表示LRU順序。
final boolean accessOrder;
那麼它內部是怎麼維護鏈表的順序的呢?
我們先回顧一下HashMap源碼解讀中的put,get方法,在putVal方法中有兩個函數調用
:
afterNodeAccess(e);
afterNodeInsertion(evict);
從字面的意思上可以知道,afterNodeAccess是節點被訪問後的函數調用,afterNodeInsertion是節點被插入後的函數調用;而這兩個函數實際上在HashMap中是空實現
實際上,類似的函數總共有3個,它們都是空實現
// Callbacks to allow LinkedHashMap post-actions
void afterNodeAccess(Node<K,V> p) { }
void afterNodeInsertion(boolean evict) { }
void afterNodeRemoval(Node<K,V> p) { }
HashMap函數中對3個函數註釋可以知道,這3個函數是空實現,並且它們是一種回調機制,特地提供給LinkedHashMap來後序回調用的;
所以LinkedHashMap繼承自HashMap,一定重寫了HashMap的這3個方法。
// 移除節點後調用,簡單地移除就行
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 時會移除最晚的節點
// 也就是鏈表首部節點 first。
// removeEldestEntry() 默認爲 false,
// 如果需要讓它爲 true,需要繼承 LinkedHashMap 並且覆蓋這個方法的實現,
// 這在實現 LRU 的緩存中特別有用,通過移除最近最久未使用的節點,
// 從而保證緩存空間足夠,並且緩存的數據都是熱點數據。
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);
}
}
// 節點被訪問後調用
void afterNodeAccess(Node<K,V> e) { // move node to last
LinkedHashMap.Entry<K,V> last;
// 如果accessOrder爲true,也就是維護LRU鏈表
// 如果指定維護LRU,當前訪問的節點不在尾部
// 則將此節點移動到尾部
if (accessOrder && (last = tail) != e) {
// ↓
// ... -> b -> e(p) -> a -> ...
LinkedHashMap.Entry<K,V> p =
(LinkedHashMap.Entry<K,V>)e, b = p.before, a = p.after;
// 首先after置爲空,表示自己會被移動到尾部
p.after = null;
// 將p在鏈表處中斷了的位置接起來
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;
}
}
這裏要注意第二個方法中對removeEldestEntry(first)方法的解讀。主要能夠實現減少LRU對空間的巨大消耗。下面是實現的一個簡單LRU。
// Reference to cyc2018
class LRUCache<K, V> extends LinkedHashMap<K, V> {
private static final int MAX_ENTRIES = 3;
protected boolean removeEldestEntry(Map.Entry eldest) {
return size() > MAX_ENTRIES;
}
LRUCache() {
super(MAX_ENTRIES, 0.75f, true);
}
}
public static void main(String[] args) {
LRUCache<Integer, String> cache = new LRUCache<>();
cache.put(1, "a");
cache.put(2, "b");
cache.put(3, "c");
cache.get(1);
cache.put(4, "d");
System.out.println(cache.keySet());
}
/** 運行結果
[3, 1, 4]
*/