设计LRU缓存结构
核心思想
当缓存满时执行淘汰算法, 删除缓存中最久未使用的key-value
解决思路
定义一个Node节点,包含前向和后向指针,使用map存储key-node,同时每次操作数据,插入新数据和更新数据,都将数据放置双向链表的头部,保证链表尾部的节点为最久未使用的数据,缓存满时就从表位删除节点。
具体代码
1 定义Node
class Node {
int key;
int val;
//前向指针
Node pre;
//后继指针
Node next;
public Node(int key, int val,Node prev, Node next) {
this.key = key;
this.val = val;
this.pre = pre;
this.next = next;
}
}
2 初始化
//哈希表
private Map<Integer,Node> map = new HashMap<>();
//设置一个头节点
private Node head;
//设置一个尾节点
private Node tail;
//设置容量
private int capacity;
//记录已使用的容量
private int used;
3 set(int key, int val)
public void set(int key, int value) {
//如果 key 已存在,直接修改值,并移动到链表头部
if(map.containsKey(key)){
map.get(key).val = value;
//更新节点的状态。将节点移动到头部
makeRecently(key);
return;
}
//如果达到容量上限,就移除尾部节点,注意HashMap要remove
if(used == capacity){
//删除尾部节点
map.remove(tail.key);
//尾节点更新
tail = tail.pre;
tail.next = null;
//已使用容量减一
used--;
}
//头结点为空,单独处理
if(head == null){
head = new Node(key, value, null,null);
tail = head;
}else{
//头节点不为空,将节点插入头部
Node t = new Node(key, value, null, head);
head.pre = t;
head = t;
}
//所以map 存的永远是头节点
map.put(key, head);
used++;
}
4 get(int key)
public int get(int key) {
// 判断是有
if(!map.containsKey(key)){
//没有值返回-1
return -1;
}
//有就返回值,但是get操作也需要更新节点状态
makeRecently(key);
return map.get(key).val;
}
5 重头戏,更新节点状态的函数 makeRecantly(int key),可以看图理解
private void makeRecently(int key){
//获取节点,将节点移动到头部
Node t = map.get(key);
//更新的节点不是头节点
if(t != head){
//如果是尾节点,更新尾部节点
if(t == tail){
tail = tail.pre;
tail.next = null;
}
else{
//不是尾部节点,更新前后指针,具体看图理解
t.pre.next = t.next;
t.next.pre = t.pre;
}
//因为t移动到了头部,所以前指针为空,后指针为旧的头节点
t.pre = null;
t.next = head;
head.pre = t;
head = t;
}
}