一 成員變量解析
/**
* 雙向鏈表頭節(jié)點
*/
transient LinkedHashMap.Entry<K,V> head;
/**
* 雙向鏈表尾節(jié)點
*/
transient LinkedHashMap.Entry<K,V> tail;
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);
}
}
/**
* true按訪問排序 false按插入排序
*/
final boolean accessOrder;
二 關(guān)鍵方法解析
1 添加元素
//該方法繼承于HashMap,LinkedHashMap只重寫了newNode()和afterNodeAccess()
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
Node<K,V>[] tab; Node<K,V> p; int n, i;
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
if ((p = tab[i = (n - 1) & hash]) == null)
// LinkedHashMap重寫newNode()方法
tab[i] = newNode(hash, key, value, null);
else {
Node<K,V> e; K k;
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p;
else if (p instanceof TreeNode)
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
else {
for (int binCount = 0; ; ++binCount) {
if ((e = p.next) == null) {
p.next = newNode(hash, key, value, null);
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
treeifyBin(tab, hash);
break;
}
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
p = e;
}
}
if (e != null) { // existing mapping for key
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
// LinkedHashMap重寫該方法
// 如果key對應(yīng)的entry存在矮嫉,按訪問更新鏈表順序
afterNodeAccess(e);
return oldValue;
}
}
++modCount;
if (++size > threshold)
resize();
afterNodeInsertion(evict);
return null;
}
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);
linkNodeLast(p);
return p;
}
// 將節(jié)點插入鏈表尾部趋距,保證插入的順序不變
private void linkNodeLast(LinkedHashMap.Entry<K,V> p) {
LinkedHashMap.Entry<K,V> last = tail;
tail = p;
// 如果尾部為null 則將p設(shè)為頭部
if (last == null)
head = p;
else {
// 如果last不為null 則將p放到last后面粒氧,至此p為尾部節(jié)點
p.before = last;
last.after = p;
}
}
// LinkedHashMap將此函數(shù)重寫,用于當(dāng)訪問某一節(jié)點后节腐,將該節(jié)點放到尾部
// 所以最近最少訪問的節(jié)點在頭部外盯,最頻繁訪問的節(jié)點在尾部
void afterNodeAccess(Node<K,V> e) { // move node to last
LinkedHashMap.Entry<K,V> last;
// 若accessOrder為true且節(jié)點e不是尾節(jié)點,則設(shè)置e為tail節(jié)點
if (accessOrder && (last = tail) != e) {
LinkedHashMap.Entry<K,V> p =
(LinkedHashMap.Entry<K,V>)e, b = p.before, a = p.after;
p.after = null;
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;
}
}
2 獲取元素
public V get(Object key) {
Node<K,V> e;
if ((e = getNode(hash(key), key)) == null)
return null;
// 若accessOrder為true翼雀,則將訪問的節(jié)點放入鏈表尾部
if (accessOrder)
afterNodeAccess(e);
return e.value;
}
LinkedHashMap 繼承了 HashMap饱苟,是 HashMap 的子類,所以其他方法和 HashMap 基本相同狼渊,在此不必贅述
三 LinkedHashMap可以實現(xiàn)LRU緩存調(diào)度算法
前面講了LinkedHashMap添加元素箱熬,刪除类垦、修改元素就不說了,比較簡單城须,和HashMap+LinkedList的刪除蚤认、修改元素大同小異,下面講一個新的內(nèi)容糕伐。
LinkedHashMap可以用來作緩存砰琢,比方說LRUCache,看一下這個類的代碼良瞧,很簡單陪汽,就十幾行而已:
public class LRUCache extends LinkedHashMap {
public LRUCache(int maxSize)
{
super(maxSize, 0.75F, true);
maxElements = maxSize;
}
protected boolean removeEldestEntry(java.util.Map.Entry eldest)
{
return size() > maxElements;
}
private static final long serialVersionUID = 1L;
protected int maxElements;
}
LinkedHashMap 可以實現(xiàn)LRU算法的緩存基于兩點:
- LinkedList 首先它是一個 Map,Map是基于K-V的褥蚯,和緩存一致
- LinkedList 提供了一個 boolean 值可以讓用戶指定是否實現(xiàn)LRU
那么挚冤,首先我們了解一下什么是LRU:LRU即Least Recently Used,最近最少使用赞庶,也就是說训挡,當(dāng)緩存滿了,會優(yōu)先淘汰那些最近最不常訪問的數(shù)據(jù)歧强。比方說數(shù)據(jù)a舍哄,1天前訪問了;數(shù)據(jù)b誊锭,2天前訪問了,緩存滿了弥锄,優(yōu)先會淘汰數(shù)據(jù)b丧靡。
我們看一下LinkedList帶boolean型參數(shù)的構(gòu)造方法:
public LinkedHashMap(int initialCapacity,
float loadFactor,
boolean accessOrder) {
super(initialCapacity, loadFactor);
this.accessOrder = accessOrder;
}
就是這個accessOrder,它表示:
(1)false籽暇,所有的Entry按照插入的順序排列
(2)true温治,所有的Entry按照訪問的順序排列
第二點的意思就是,如果有1戒悠,2熬荆,3這3個Entry,那么訪問了1绸狐,就把1移到尾部去卤恳,即2,3寒矿,1突琳。每次訪問都把訪問的那個數(shù)據(jù)移到雙向隊列的尾部去,那么每次要淘汰數(shù)據(jù)的時候符相,雙向隊列最頭的那個數(shù)據(jù)不就是最不常訪問的那個數(shù)據(jù)了嗎拆融?換句話說,雙向鏈表最頭的那個數(shù)據(jù)就是要淘汰的數(shù)據(jù)。
“訪問”镜豹,這個詞有兩層意思:
(1)根據(jù)Key拿到Value傲须,也就是get方法
(2)修改Key對應(yīng)的Value,也就是put方法