引言
最近在找工作,我是夜夜学啊天天面,天天面啊天天挂,大多都是挂在算法上,刷题太少又刷不动,谁懂啊家人们,但是进大厂算法不过关除非欧皇每轮都能遇到easy,要不然真的是刷面经啊(比如我)。 除了算法,另外最近在面试大厂的时候总会被问到Redis缓存淘汰策略,那问到这个地步的面试官肯定少不了考咱一道LRU缓存的实现了,知己知彼百战百胜,要想拿捏面试官,手撕LRU少不了。
一般情况下,面试官会问你怎么用JAVA集合类实现一个LRU了,其实Java本身有的基础集合类就有类似实现,那就是LinkedHashMap了,答道LinkedHashMap在面试官心里仅仅是你知道LRU,但是要实现一般情况下都是要你不用LinkedHashMap来实现了,正常让你实现一个get和put方法,可以简单把LRU想象成队列,队头存储的元素都是最近高频使用的元素,而队尾则恰恰相反,按照这个思想我门来实现一下
get方法
get方法的核心逻辑就是获取不到那就是没有,如果获取到了这个元素那就需要将这个元素移动到队头。
put方法
put方法的核心就是先看队列里面有没有这个元素,
-
如果有那就直接更新,更新之后再把这个元素移动到队头 -
如果没有这个元素那就是新增一个节点并且移动到队头,如果新增之后队列的容量超过了额定容量还需要再移除队尾的元素
实现
先定义一个双向链表的节点类
/**
* 自己构造一个双向链表节点
*/
class DoubleLinkedNode {
/**
* 键
*/
int key;
/**
* 值
*/
int value;
/**
* 前驱节点
*/
DoubleLinkedNode prev;
/**
* 后继节点
*/
DoubleLinkedNode next;
/**
* 构造方法
*/
public DoubleLinkedNode() {}
public DoubleLinkedNode(int key, int value) {
this.key = key;
this.value = value;
}
}
LRUCache实现
public class LRUCache {
/**
* 哈希表存储数据
*/
private Map<Integer,DoubleLinkedNode> cache;
/**
* 双向链表头节点,不存储任何值,标识作用
*/
private DoubleLinkedNode head;
/**
* 双向链表尾节点,不存储任何值,标识作用
*/
private DoubleLinkedNode tail;
/**
* 双向链表长度
*/
private int size;
/**
* 最大容量
*/
private int capacity;
/**
* 构造方法
*/
public LRUCache(int capacity) {
this.size = 0;
this.capacity = capacity;
this.cache = new HashMap<>();
this.head = new DoubleLinkedNode();
this.tail = new DoubleLinkedNode();
head.next = tail;
tail.prev = head;
}
public int get(int key){
DoubleLinkedNode node = cache.get(key);
if (node == null){
return -1;
}
// 移动当前节点至队头
moveToHead(node);
// 返回该值
return node.value;
}
/**
* 添加元素
* @param key
* @param value
*/
public void put(int key ,int value){
DoubleLinkedNode node = cache.get(key);
// 该元素不存在则需要新加一个节点再移动到队头
if (node == null){
node = new DoubleLinkedNode(key, value);
// 添加到哈希表中
cache.put(key,node);
// 双向链表长度+1
size++;
// 添加到链表头部
addToHead(node);
// 如果总元素个数大于容量,需要移除队尾元素
if (size > capacity){
// 删除链表中最后一个元素
DoubleLinkedNode tailNode = removeTail();
// 同时从哈希表中删除该元素
cache.remove(tailNode.key);
//长度-1
size --;
}
}else {
// 该元素存在则修改该值,并且移动到队头
node.value = value;
moveToHead(node);
}
}
/**
* 添加节点到链表头部
* @param node
*/
public void addToHead(DoubleLinkedNode node){
node.next = head.next;
node.prev = head;
node.next.prev = node;
head.next = node;
}
/**
* 删除双向链表中的节点
*/
public void removeNode(DoubleLinkedNode node){
node.next.prev = node.prev;
node.prev.next = node.next;
node.prev = null;
node.next = null;
}
/***
* 将一个节点移动到双向链表头部
* @param node
*/
public void moveToHead(DoubleLinkedNode node){
removeNode(node);
addToHead(node);
}
/**
* 移除双向链表的最后一个节点
*/
public DoubleLinkedNode removeTail(){
DoubleLinkedNode node = tail.prev;
removeNode(node);
return node;
}
}
示意图(方便理解增加节点的过程)
1.增加节点
2.删除节点
有什么问题评论区一起探讨