LinkedHashMap 实现LRU缓存

时间:2022-11-07 00:14:27

date: 2020-07-09 13:52:00

updated: 2020-07-21 17:40:00

LinkedHashMap 实现LRU缓存

参考

LinkedHashMap是HashMap的子类,但是内部还有一个双向链表维护键值对的顺序,每个键值对既位于哈希表中,也位于双向链表中。LinkedHashMap支持两种顺序插入顺序 、 访问顺序

插入顺序:先添加的在前面,后添加的在后面。修改操作不影响顺序

访问顺序:所谓访问指的是get/put操作,对一个键执行get/put操作后,其对应的键值对会移动到链表末尾,所以最末尾的是最近访问的,最开始的是最久没有被访问的,这就是访问顺序。

public LinkedHashMap(int initialCapacity, float loadFactor, boolean accessOrder) 其中参数accessOrder就是用来指定是否按访问顺序,如果为true,就是访问顺序。

LRU(Least Recently Used): 最近最少使用

public class LRUCache<K, V> extends LinkedHashMap<K, V> {

    private int maxEntries;

    public LRUCache(int maxEntries) {
super(16, 0.75f, true);
this.maxEntries = maxEntries;
} @Override
protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {
return size() > maxEntries;
}
}

在LinkedHashMap添加元素后,会调用removeEldestEntry防范,传递的参数时最久没有被访问的键值对,如果方法返回true,这个最久的键值对就会被删除。LinkedHashMap中的实现总返回false,该子类重写后即可实现对容量的控制。

LRUCache<String,Object> cache = new LRUCache<>(3);
cache.put("a","abstract");
cache.put("b","basic");
cache.put("c","call");
cache.get("a");
cache.put("d","滴滴滴");
System.out.println(cache); // 输出为:{c=call, a=abstract, d=滴滴滴}

相比HashMap,LinkedHashMap还实现了三个方法,当且仅当 accessOrder=True 时会被调用到

void afterNodeAccess(Node<K,V> p) { }  //访问节点之后调用的方法
void afterNodeInsertion(boolean evict) { } //插入节点之后调用的方法
void afterNodeRemoval(Node<K,V> p) { } //删除节点之后调用的方法
在get()方法中调用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;
}
}
LinkedHashMap的put()是调用的父类HashMap的put()方法
这个方法是在HashMap中的put()方法中被调用,在LinkedHashMap中被实现,具体作用是:在插入新节点后,因为缓存不够,需要删除最近最少使用的节点。要成功调用这个方法,还需要用户覆写 removeEldestEntry(first) 方法
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);
}
}
当LinkedHashMap成功调用removeNode()方法,删除节点之后,会调用本方法
具体作用是:在removeNode方法中,只是删除了HashMap中的节点,并没有在链表中删除。所以在removeNode中,回调了这个方法,将该节点从链表中删除(这里是删除的头结点,因为头结点是最早进入或者最近最久未使用的)。
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;
}

LinkedHashMap 实现LRU缓存