LRU缓存都不会写就敢出来面试?

116 阅读3分钟

引言

最近在找工作,我是夜夜学啊天天面,天天面啊天天挂,大多都是挂在算法上,刷题太少又刷不动,谁懂啊家人们,但是进大厂算法不过关除非欧皇每轮都能遇到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.增加节点

image.png

2.删除节点

image.png

有什么问题评论区一起探讨