概述
LRU(Least Recently Used)是一种常见的缓存淘汰策略,用于在缓存容量不足时确定要淘汰的缓存项。LRU策略基于一种简单的思想:最近最少使用的项最先被淘汰。
工作原理
1.当一个数据被访问时,它就被标记为最近使用过的。
2.当缓存满了且需要淘汰一个数据项时,LRU算法会选择最近最少使用的数据项进行淘汰。
实现
下面我们采用双向链表 + 哈希表的方式手写一个LRU算法。
底层数据结构
// Hash表,用于快速查找节点
private Map<Integer,Node> cache;
// 缓存容量,当超过容量需要淘汰
private int capacity;
// 链表头节点,标记最近访问的节点
private Node head;
// 链表尾节点,标记最旧的节点
private Node tail;
// 记录一个size,提升性能
private int size;
// Node节点信息
class Node{
int key;
int value;
Node pre;
Node next;
}
初始化
public LRUCache(int capacity) {
// 创建hash表,提前知道容量,防止map扩容,增加性能
this.cache = new HashMap<>((int)(capacity / 0.75) + 1);
// 设置容量
this.capacity = capacity;
this.size = 0;
// 构建双向链表
head = new Node();
tail = new Node();
head.next = tail;
tail.pre = head;
}
删除节点
// 移除链表中的节点
private void removeNode(Node node){
Node pre = node.pre;
Node next = node.next;
pre.next = next;
next.pre = pre;
}
添加到头节点
private void addToFront(Node node){
Node next = head.next;
head.next = node;
node.pre = head;
node.next = next;
next.pre = node;
}
移动到头节点
public void moveToHead(Node node) {
if(head.next == node){
return;
}
removeNode(node);
addToHead(node);
}
操作hash表
// get获取元素
public int get(int key){
public int get(int key) {
Node node = cache.get(key);
if (node == null) {
return -1;
}
moveToHead(node);
return node.value;
}
}
// add增加元素
public void put(int key,int value){
Node node = cache.get(key);
if (node != null) {
node.value = value;
moveToHead(node);
return;
}
Node newNode = new Node();
newNode.key = key;
newNode.value = value;
cache.put(key,newNode);
addToHead(newNode);
size++;
if (size > capacity) {
cache.remove(tail.pre.key);
removeNode(tail.pre);
size--;
}
}
完整代码
public class LRUCache {
private Map<Integer, Node> cache;
private int capacity;
private int size;
private Node head ;
private Node tail ;
public LRUCache(int capacity) {
this.cache = new HashMap<>((int)(capacity / 0.75) + 1);
this.capacity = capacity;
this.size = 0;
head = new Node();
tail = new Node();
head.next = tail;
tail.pre = head;
}
public void addToHead(Node node) {
Node next = head.next;
head.next = node;
node.pre = head;
node.next = next;
next.pre = node;
}
public void moveToHead(Node node) {
if(head.next == node){
return;
}
removeNode(node);
addToHead(node);
}
public void removeNode(Node node) {
Node next = node.next;
Node pre = node.pre;
pre.next = next;
next.pre = pre;
}
public int get(int key) {
Node node = cache.get(key);
if (node == null) {
return -1;
}
moveToHead(node);
return node.value;
}
public void put(int key, int value) {
Node node = cache.get(key);
if (node != null) {
node.value = value;
moveToHead(node);
return;
}
Node newNode = new Node();
newNode.key = key;
newNode.value = value;
cache.put(key,newNode);
addToHead(newNode);
size++;
if (size > capacity) {
cache.remove(tail.pre.key);
removeNode(tail.pre);
size--;
}
}
@Data
class Node {
Node pre;
Node next;
int key;
int value;
}