LRU是什么
LRU的中文名字为最近最少使用缓存,一言以蔽之,当加入元素超过存储容量的时候会剔除最少使用过的元素。并且需要get()操作的时间复杂度为O(1)。
LRU有什么作用
LRU在某种程度上可以作为一种淘汰策略使用,比如redis的淘汰策略中就有LRU的淘汰方式。
LRU的非线程安全实现(借助一个数据结构)
通过上述描述可以知道,我们需要两种基本的数据结构,哈希表(用于get和put操作以及在O(1)时间复杂度下实现查询操作)和双向链表(可以设计为链表尾部加入最近访问的元素,链表头部剔除最近未被访问的元素) 。
在Java中同时支持哈希表和双向链表的数据结构为LinkedHashMap, 并且LinkedHashMap可以记录下键插入的顺序, 那么问题可以等价于每一次操作(不管是get还是put), 把原先的键删除,然后都把对应的键插入到链表尾部。
当操作过后超过哈希表的容量的时候,将链表头部的键值对删除即可。
class LRUCache {
//LRU的实现需要用到的数据结构为哈希表和双向链表
//在Java中可以同时支持哈希表和双向链表的数据结构为LinkedHashMap
//LinkedHashMap可以保证插入键按照时间的先后顺序, 所以每一次操作(get/put)都重新插入
//如果put操作超过LRU的容量,则将双向链表的头部元素移除
//但是这种LRU无法保证线程安全
private Map<Integer, Integer> map;
private int capacity;
public LRUCache(int capacity) {
map = new LinkedHashMap<>();
this.capacity = capacity;
}
public int get(int key) {
if (!map.containsKey(key)) {
return -1;
}
Integer val = map.get(key);
map.remove(key);
map.put(key, val);
return val;
}
public void put(int key, int value) {
//1. key存在map中, 操作方式同get
//2. key不存在map中, 新增键值对, 如果超出容量,删除老键值对
if (map.containsKey(key)) {
map.remove(key);
map.put(key, value);
return;
}
map.put(key, value);
if (map.size() > capacity) {
map.remove(map.entrySet().iterator().next().getKey());
}
}
}
LRU的非线程安全实现(借助哈希表+双端队列)
哈希表用于快速检索到元素, 双端队列队尾用于加上最新处理元素的key, 队头存放最久未处理的元素.
class LRUCache {
//HashMap + ArrayDeque(双端队列)实现
private int capacity;
private Map<Integer, Integer> map;
private Queue<Integer> keyQue;
public LRUCache(int capacity) {
this.capacity = capacity;
map = new HashMap<>();
keyQue = new ArrayDeque<>(