LRU(Least recently used)算法根据数据的历史访问记录来进行淘汰数据,其核心思想是“如果数据最近被访问过,那么将来被访问的几率也更高”。
LinkedHashMap
LinkedHashMap
继承了 HashMap
,其内部又实现了一个双向链表
public class LinkedHashMap<K,V> extends HashMap<K,V> implements Map<K,V>
{
......
static class Entry<K,V> extends HashMap.Node<K,V> {
Entry<K,V> before, after; // 双向链表
......
}
// 表头
transient LinkedHashMap.Entry<K,V> head;
// 表尾
transient LinkedHashMap.Entry<K,V> tail;
......
// 插入节点时触发
void afterNodeInsertion(boolean evict) { // possibly remove eldest
LinkedHashMap.Entry<K,V> first;
if (evict && (first = head) != null && removeEldestEntry(first)) {
K key = first.key;
// 父类HashMap方法,在其中会调用 afterNodeRemoval方法
removeNode(hash(key), key, null, false, true);
}
}
......
// 通过继这个接口,来实现元素的出队
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;
}
......
// 将刚刚访问过的节点放到链表尾部
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;
}
}
......
}
- 当调用
put
操作时,会触发afterNodeInsertion
方法,来决定是否进行删除操作 - 当调用
get
方法时,会触发afterNodeAccess
方法来对链表进行重新排序
插入数据
调用LinkedHashMap
的 put
方法,实际是调用的其父类 HashMap
的 put
的方法,再通过继承实现 afterNodeAccess
, afterNodeInsertion
来实现对链表结果的维护。
public class HashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>, Cloneable, Serializable {
....
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) {
Node<K,V>[] tab;
Node<K,V> p;
int n, i;
// 第一次使用,初始化 HASH 表
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
else {
....
if (e != null) { // 键值已经存在
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);
return oldValue;
}
}
....
afterNodeInsertion(evict);
return null;
}
....
// 方法被子类继承,实现额外操作
void afterNodeAccess(Node<K,V> p) { }
void afterNodeInsertion(boolean evict) { }
....
}
获取数据
public class LinkedHashMap<K,V> extends HashMap<K,V> implements Map<K,V>
....
public V get(Object key) {
Node<K,V> e;
if ((e = getNode(hash(key), key)) == null)
return null;
if (accessOrder)
afterNodeAccess(e);
return e.value;
}
....
}
基于 LinkedHashMap 的 LRU demo
通过继承 LinkedHashMap
可以很容易的实现 LRU
class LRUCache<K, V> extends LinkedHashMap<K, V>{
private int capacity;
public LRUCache(int capacity) {
super(capacity, 0.75f, true);
this.capacity = capacity;
}
@Override
protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {
return super.size() > capacity;
}
}
- 通过子类重写
removeEldestEntry
方法,当容量大于capacity
时,删了链表头数据,即最久末使用数据
使用Demo
class Demo{
public static void main(String[] args) {
LinkedHashMap<String, String> map = new LRUCache<>(3); // 设置容量
map.put("A", "AAA");
map.put("B", "BBB");
map.put("C", "CCC");
System.out.println(map.keySet());
System.out.println(map.get("A"));
System.out.println(map.keySet());
map.put("D", "DDD");
System.out.println(map.keySet());
}
}
运行结果
[A, B, C]
AAA
[B, C, A]
[C, A, D]