前言
LRU在实际生活中的使用场景非常多。
- 比如手机切换到任务界面,你最近使用过的几个APP总会在按照时间排在最前面。
- 比如你总是会把你最近在看的书,放在你的桌子上,书架上摆放的都是不常看的。
- 再比如,我们会把所有有用的信息记录在电脑或网盘里,但是最常用的信息,我们会牢牢地记在脑子里。
一、LRU Cache的定义
LRU Cache算法,又称为近期最少使用算法。我们会设置一个容量固定的存储空间,比如电脑/手机的内存,但是如果这个内存要被数据占满了,此时又要存入新的数据,我们就要把近期最少使用的资源从缓存里剔除掉,并把新的数据加入到缓存中。
二、设计思想
- 方框内的元素都是内存未被填满的情况,直接存入即可,但是要保证最新存入的要在队列的最上面,最久未使用的要在最下面。
- 第①步:当存入C元素时,最久未使用的元素A被剔除。而元素C本来就存在于队列中,所以要把C的位置替换到队列的最上面。
- 第②步,当存入G元素时,剔除最久未使用的B元素,并且把G插在队列最上方。 这就是LRU的基本思想
三、具体实现
LRU Cache实现的要素主要有两个:缓存容量和替换策略
要求查询、修改和更新任意元素的时间复杂度为O(1)。
这里我们用hash表和双向链表来实现LRU
双向链表用来模拟最久先出的队列。HashMap用来存储节点标识和节点对象的关系,这样就能实现查询、修改和更新任意元素的时间复杂度为O(1)
public class LRUCache {
static class Node {
public int key, val;
public Node(int k, int v) {
this.key = k;
this.val = v;
}
}
// LRU容量
int capacity;
// HashMap
HashMap<Integer, Node> map;
// 双向链表
LinkedList<Node> cache;
public LRUCache(int capacity) {
this.capacity = capacity;
map = new HashMap<>(capacity);
cache = new LinkedList<>();
}
public int get(int key) {
if (!map.containsKey(key)) return -1;
Node node = map.get(key);
// 获取到了元素之后,要把获取的元素排到队列的最上面
cache.remove(node);
cache.addFirst(node);
return node.val;
}
public void put(int key, int value) {
Node node = new Node(key, value);
if (map.containsKey(key)) {
// 把已经存在的值,从链表中remove掉
cache.remove(map.get(key));
} else if (capacity == map.size()) {
// 如果链表已经达到了容量,把链表最后的元素remove掉
Node last = cache.removeLast();
map.remove(last.key);
}
// 将最新放入的元素排在队列最前面
cache.addFirst(node);
map.put(key, node);
}
}