如何自己实现一个LRU Cache
LRU是Least Recently Used的缩写,即最近最少使用的淘汰,在内存有限的情况下,可以使用这种算法,保持内存中是最热的数据。
思路
lru
有两种实现方法,可以通过HashMap+双向链表
的形式实现,保证最近访问的放在尾部,最长时间未访问的放在头部就可以了,也可以直接通过LinkedHashMap
实现。
实现
HashMap+双向链表实现
public class LruCache {
/**
* 缓存容量
*/
private int capacity;
/**
* 头结点
*/
private Node head;
/**
* 尾结点
*/
private Node tail;
/**
*
*/
private Map<String, Node> cache;
public LruCache(int capacity) {
this.capacity = capacity;
cache = new HashMap<>();
head = new Node(null, null);
tail = new Node(null, null);
head.next = tail;
tail.pre = head;
}
class Node {
String key;
Object value;
Node pre;
Node next;
Node(String key, Object value) {
this.key = key;
this.value = value;
}
}
public void set(String key, Object value) {
Node node = cache.get(key);
// 已存在,更新value并且移动到尾部
if (Objects.nonNull(node)) {
node.value = value;
cache.put(key, node);
node.pre.next = node.next;
node.next.pre = node.pre;
nodeMoveToTail(node);
return;
}
// cache已满,移除head元素
if (cache.size() == capacity) {
Node tmp = head.next;
head.next = head.next.next;
head.next.pre = head;
cache.remove(tmp.key);
}
node = new Node(key, value);
nodeMoveToTail(node);
cache.put(key, node);
}
public Object get(String key) {
Node node = cache.get(key);
if (Objects.nonNull(node)) {
node.pre.next = node.next;
node.next.pre = node.pre;
nodeMoveToTail(node);
return node.value;
}
return null;
}
private void nodeMoveToTail(Node node) {
node.next = tail;
node.pre = tail.pre;
tail.pre.next = node;
tail.pre = node;
}
LinkedHashMap实现
HashMap
+LinkedList
???这不就是LinkedHashMap
吗,LinkedHashMap
就是在HashMap
的基础上增加了链表,用于维护元素的顺序,使用LinkedHashMap
实现就比较简单了,代码如下:
public class LruCache {
/**
* 缓存容量
*/
private int capacity;
private Map<String, Object> cache;
public LruCache(int capacity) {
this.capacity = capacity;
// 记录访问顺序设置为true
cache = new LinkedHashMap<String, Object>(capacity, 0.75f, true) {
@Override
protected boolean removeEldestEntry(Map.Entry<String, Object> eldest) {
return size() > capacity;
}
};
}
public Object get(String key) {
return cache.get(key);
}
public void set(String key, Object value) {
cache.put(key, value);
}
@Override
public String toString() {
return "LruCache{" +
"cache=" + cache.toString() +
'}';
}
}
验证
public static void main(String[] args) {
LruCache cache = new LruCache(3);
cache.set("key1", 1);
cache.set("key2", 2);
cache.set("key3", 3);
System.out.println(cache.toString());
cache.get("key1");
System.out.println(cache.toString());
cache.set("key4", 4);
System.out.println(cache.toString());
}
总结
基于LinkedHashMap
的缓存实现在很多地方都有实现,需要平时用心去观察。比如在SpringBoot中的SpringBootConfigurationFinder
类
final class SpringBootConfigurationFinder {
private static final Map<String, Class<?>> cache = Collections.synchronizedMap(new SpringBootConfigurationFinder.Cache(40));
...省略
private static class Cache extends LinkedHashMap<String, Class<?>> {
private final int maxSize;
Cache(int maxSize) {
super(16, 0.75F, true);
this.maxSize = maxSize;
}
protected boolean removeEldestEntry(Entry<String, Class<?>> eldest) {
return this.size() > this.maxSize;
}
}
}
可以看出直接实现的lru缓存因为继承的是基础的Map类,所以是线程不安全的,在多线程环境下请包装成线程安全的Map类进行使用。