一、Lru的概念
Least recently used (Lru的全称),一般我们翻译为最近做少使用。
二、分析LinkedHashMap
首先看一下它的get方法:
public V get(Object key) {
Node<K,V> e;
if ((e = getNode(hash(key), key)) == null)
return null;
//这里是关键点,如果accessOrder为true的话,就需要执行afterNodeAccess
if (accessOrder)
afterNodeAccess(e);
return e.value;
}
afterNodeAccess方法比较绕,但是它整体实现的功能是将传入的节点放到链表的尾部。
void afterNodeAccess(Node<K,V> e) { // move node to last
LinkedHashMap.Entry<K,V> last;
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;
}
}
再看它的putVal方法(注意:LinkedHashMap复用了HashMap的put方法):
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;
//在上面的代码中我们解释了这段代码的作用,将e节点放到双向链表的尾部
//所以在LinkedHashMap中的添加的新元素被放到了链表的尾部。
afterNodeAccess(e);
return oldValue;
}
//...
//这部分代码在HashMap中是空实现,在LinkedHashMap中有具体实现
afterNodeInsertion(evict);
}
下面这段代码的作用是删除头节点,通过代码也比较容易看出来:
void afterNodeInsertion(boolean evict) { // possibly remove eldest
LinkedHashMap.Entry<K,V> first;
//这里的removeEldestEntry方法是实现lru的关键点
if (evict && (first = head) != null && removeEldestEntry(first)) {
K key = first.key;
removeNode(hash(key), key, null, false, true);
}
}
经过上面的分析,我们可以做出如下总结:
- LinkedHashMap的get操作会将操作的元素移动到链表末尾;
- LinkedHashMap的put操作会将新插入的元素移动到链表末尾;
- LinkedHashMap的put操作会根据removeEldestEntry(first)判断是否删除头节点。
上面的三点我们可以看出新插入和新访问的元素都会在链表的尾部,而头部的元素自然就是最近最少访问的节点,所以LinkedHashMap满足Lru的定义,但是还有一个关键点,就是如何设置Lru的大小,上面的代码中已经提示了,我们可以重写removeEldestEntry方法来空值LinkedHashMap的长度:
@Override
protected boolean removeEldestEntry(Map.Entry eldest) {
return size() > cacheSize;
}