LRU全称Least Recently Used,表示最近最少使用,第一次听到这个概念是在大三的操作系统课程里面,用于页面置换算法,为虚拟页式存储管理服务。
LRU算法的提出基于一个事实,在前面几条指令中使用频繁的页面很可能在接下来的几条指令中也会频繁使用,反过来很久没有使用过的那就认为接下来也不会使用,这就是局部性原理
现在LRU算法也经常用作缓存淘汰策略。
LeetCode146. LRU 缓存就是这样一道题,据说这也是面试常考的一个点
其实如果了解了原理用对了方法就不难了
很明显这个缓存是有顺序,淘汰缓存时候从双向链表尾部淘汰,添加和get方法都将节点放到头处,跟着代码思路下来并不难
public class LRUCache {
private Map<Integer, MyLinkedNode> cache = new HashMap<Integer, MyLinkedNode>();
private int capacity;//初始化容量
private int size;//缓存现有数量
private MyLinkedNode head,tail;
public LRUCache(int capacity) {
this.size = 0;
this.capacity = capacity;
head = new MyLinkedNode();
tail = new MyLinkedNode();
head.next = tail;
tail.prev = head;
}
public int get(int key) {
MyLinkedNode node = cache.get(key);
if(node == null){
return -1;
}
moveToHead(node);//每次get就将节点放进头,表示使用多
return node.value;
}
public void put(int key, int value) {
MyLinkedNode node = cache.get(key);
if(node == null){//不存在这个节点就创建一个添加进缓存
MyLinkedNode newNode = new MyLinkedNode(key,value);
cache.put(key,newNode);
addToHead(newNode);
++size;
if(size > capacity){
//容量超出就删除链表的尾部节点
MyLinkedNode tail = deleteTail();
cache.remove(tail.key);//根据这个key作索引删除cache里面的键值对
--size;
}
}else {//这个key存在了的话,就修改value并且移动到头部
node.value = value;
moveToHead(node);
}
}
private void addToHead(MyLinkedNode node){//将节点添加到链表头
node.prev = head;
node.next = head.next;
head.next.prev = node;
head.next = node;
}
private void moveToHead(MyLinkedNode node){//将链表中的节点转移到头
removeNode(node);
addToHead(node);
}
private void removeNode(MyLinkedNode node){//删除节点
node.prev.next = node.next;
node.next.prev = node.prev;
}
private MyLinkedNode deleteTail(){//删除尾节点
MyLinkedNode res = tail.prev;
removeNode(res);
return res;
}
class MyLinkedNode{//双向链表节点
int key;
int value;
MyLinkedNode prev;
MyLinkedNode next;
public MyLinkedNode(){}
public MyLinkedNode(int key,int value){
this.key = key;
this.value = value;
}
}
public static void main(String[] args) {
LRUCache cache = new LRUCache(2);
cache.put(1,1);
cache.put(2, 2);
System.out.println(cache.get(1));
cache.put(3, 3);
System.out.println(cache.get(2));
cache.put(4, 4);
System.out.println(cache.get(1));
System.out.println(cache.get(3));
System.out.println(cache.get(4));
}
}
我看这个题目的评论区的时候有人说,自定义的那个节点为什么要有key这个属性,cache变量不就是根据key存的吗,节点里面只放一个value不好吗
如果没有key的话我们进行淘汰缓存的时候要删除尾部节点,注意我们删除的这个节点仅仅是将双向链表中的节点取消了引用,但是这个节点在map类型的cache里面还是真实存在的,我们如果没有节点里面的key拿什么做索引来删除cache里面的键值对。这就是自定义链表节点中维护key的关键所在