1. 概述
LinkedHashMap
繼承了HashMap
,並在其基礎上維護了一條雙向鏈表,用來保證順序訪問。
2. 源碼分析
2.1 內部類和屬性
LinkedHashMap
的內部類繼承了Node
,並根據需要增加了before,after屬性。這兩個屬性你肯定似曾相識,在LinkedList
中使用過。其實他們的功能其實是一樣的,定位前一個或後一個entry
。
// 頭結點
transient LinkedHashMap.Entry<K,V> head;
// 尾節點
transient LinkedHashMap.Entry<K,V> tail;
// 排序方式,true:訪問順序排序(LRU) false:插入順序排序
final boolean accessOrder;
// 其它的屬性都在hashmap中,這裏就不一一列出了
// LinkedHashMap的內容存儲類
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);
}
}
2.2 構造方法
通過下面的代碼可以看出,LinkedHashMap
的構造方法基本是調用父類構造來完成的,自己只是完成accessOrder
屬性操作(也就是排序方式,默認false)。
public LinkedHashMap() {
super();
accessOrder = false;
}
public LinkedHashMap(int initialCapacity) {
super(initialCapacity);
accessOrder = false;
}
public LinkedHashMap(int initialCapacity, float loadFactor) {
super(initialCapacity, loadFactor);
accessOrder = false;
}
public LinkedHashMap(Map<? extends K, ? extends V> m) {
super();
accessOrder = false;
putMapEntries(m, false);
}
// 顯式指定是否啓用有序訪問
public LinkedHashMap(int initialCapacity,
float loadFactor,
boolean accessOrder) {
super(initialCapacity, loadFactor);
this.accessOrder = accessOrder;
}
2.3 插入方法和鉤子方法
LinkedHashMap
的元素獲取是調用HashMap
的put
方法,不過LinkedHashMap
通過重寫HashMap
的鉤子方法來實現一些自己的邏輯。
// HashMap
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
…… 省略無關代碼
if (e != null) { // existing mapping for key
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e); // 鉤子方法
return oldValue;
}
}
if (++size > threshold)
resize();
afterNodeInsertion(evict); // 鉤子方法
return null;
}
// 鉤子方法的定義(在HashMap中爲空實現,留給子類重寫邏輯)
void afterNodeAccess(Node<K,V> p) { }
void afterNodeInsertion(boolean evict) { }
void afterNodeRemoval(Node<K,V> p) { }
// LinkedHashMap
// 節點訪問後,移動元素e到鏈尾,頻繁訪問的元素就會落在尾部
void afterNodeAccess(Node<K,V> e) { // move node to last
LinkedHashMap.Entry<K,V> last;
// accessOrder爲null且tail節點不是e,纔會進入方法
if (accessOrder && (last = tail) != e) {
LinkedHashMap.Entry<K,V> p =
(LinkedHashMap.Entry<K,V>)e,
// b爲e的前驅節點,a爲p的後繼節點
b = p.before, a = p.after;
p.after = null;
if (b == null)
head = a; // b=null說明e爲頭結點,則將後繼a設爲頭節點
else
b.after = a; // b!=null,將b的後繼設置爲a
if (a != null)
a.before = b; // a!=null,將a的前驅設置爲b
else
last = b; // a=null則將last設置爲b
if (last == null)
head = p; // p爲頭結點
else {
p.before = last;
last.after = p; // 設置p和last的鏈接關係
}
tail = p; // 設置p爲尾節點
++modCount;
}
}
// 在put方法調用到這裏evict爲true
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); // 在HashMap中,這裏不再解釋
}
}
// 這個方法總是返回false,你可能會疑惑這個鬼東西是否有問題。
// 其實這個方法是一個鉤子方法,留給你自己來實現決定是否刪除first(LRU算法實現)
protected boolean removeEldestEntry(Map.Entry<K,V> eldest) {
return false;
}
// 節點刪除後斷開鏈接
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;
}
2.4 獲取方法
瞭解了hashmap
的getNode
方法和上邊的鉤子方法,那麼這裏看起來就比較無腦了,沒啥邏輯。
public V get(Object key) {
Node<K,V> e;
// 調用hashmap的getNode獲取entry
if ((e = getNode(hash(key), key)) == null)
return null;
// 決定是否更新e到鏈尾
if (accessOrder)
afterNodeAccess(e);
return e.value;
}
2.5 移除方法
移除方法更沒營養,調用HashMap
的remove
方法時調用了一下afterNodeRemoval
方法。
2.6 LRU緩存實現
主要內容其實就是重寫LinkedHashMap
的removeEldestEntry
方法,Mybatis
也用LinkedHashMap
實現LRU算法,也是這個套路,懂就行了
// 摘抄自石杉的LRU實現
class LRUCache<K, V> extends LinkedHashMap<K, V> {
private final int CACHE_SIZE;
/**
* 傳遞進來最多能緩存多少數據
*
* @param cacheSize 緩存大小
*/
public LRUCache(int cacheSize) {
// true 表示讓 linkedHashMap 按照訪問順序來進行排序,最近訪問的放在頭部,最老訪問的放在尾部。
super((int) Math.ceil(cacheSize / 0.75) + 1, 0.75f, true);
CACHE_SIZE = cacheSize;
}
/**
* 鉤子方法,通過put新增鍵值對的時候,若該方法返回true
* 便移除該map中最老的鍵和值
*/
@Override
protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {
// 當 map中的數據量大於指定的緩存個數的時候,就自動刪除最老的數據。
return size() > CACHE_SIZE;
}
}
3. 總結
總結好難,不總結了。