LRU 算法实践
「这是我参与2022首次更文挑战的第28天,活动详情查看:2022首次更文挑战」。
redis 的 LRU 算法简介
题目:1、redis 的 lru 了解过吗?是否可以手写一个 lru 算法?
什么是 LRU?
LRU 是 Least Recently Used 的缩写,即最近最少使用,是一种常用的页面置换算法。
选择最近最久未使用的数据予以淘汰。
算法设计来源
力扣(146 题目 LRU 算法简介):
如图:
设计思想
1、所谓缓存,必须要有读 + 写两个操作,按照命中率的思路考虑,写操作 + 读操作时间复杂度都需要为 O(1)。
2、 特征要求
2.1 必须要有顺序之分,一区分最近使用的和很久没有使用的数据排序。
2.2 写和读操作一次执行。
2.3 如果容量(坑位)满了要删除最不长用的谁,每次新访问还要把新的数据出入到队头(按照业务你自己设定左右那一边是队头)。
查找快、插入快、删除快、且还要先后排序 -----> 什么样的数据结构可以满足这个问题?
你是否可以在 O(1) 的时间复杂度内完成这两种操作?
如果一次就可以找到,你觉得什么数据结构最合适???
LRU 的算法核心是哈希链表
本质就是 HashMap + DoubleLinkedList
时间复杂是O(1) ,哈希+双向链表的结合体。
LRU 算法动画
LRU 手动编码实现
实现案例一
参考: LinkedHashMap
依赖 jdk
public class Q149<K, V> extends LinkedHashMap<K, V> {
private int capacity; // 缓存坑位
public Q149(int capacity) {
// true 是访问顺序
// false 是插入顺序
super(capacity, 0.75F, true);
this.capacity = capacity;
}
@Override
protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {
return super.size() > capacity;
}
public static void main(String[] args) {
Q149 q149 = new Q149(3);
q149.put(1, "a");
q149.put(2, "b");
q149.put(3, "c");
System.out.println(q149.keySet());
q149.put(4, "d");
q149.put(5, "e");
System.out.println(q149.keySet());
}
}
实现案例二
不依赖 jdk
数据结构示意:
思路解释:
1、 参照 HashMap 的做法先创建一个, Node 节点对象,包含 k, v 属性 和前驱,后继对象指针。
2、 构建一个双端链表 DoubleLinkList
实现缓存数据链表结构。主要包含 addHead
添加到头节点,作用是用来更新新访问的 Node, 还有一个就是 removeNode
删除节点。以及 getLastNode
获取尾部节点。
3、通过 Map
和 DoubleLinkList
联合起来共同组建一个 LRU 结构。整体如上图所示
public class Q149_1 {
// map 负责查找,构建一个虚拟的双向链表, 它里面安装的就是一个 Node 节点,作为数据的载体
// 1 构造一个 Node 节点,作为数据的载体
class Node<K, V> {
K key;
V value;
Node<K, V> prev;
Node<K, V> next;
public Node() {
this.prev = this.next = null;
}
public Node(K key, V value) {
this.key = key;
this.value = value;
this.prev = this.next = null;
}
}
//2 构建一个双向队列,里面存放的就是我们的 Node
class DoubleLinkList<K, V> {
Node<K, V> head;
Node<K, V> tail;
// 2.1 构造方法
public DoubleLinkList() {
this.head = new Node<>();
this.tail = new Node<>();
this.head.next = this.tail;
this.tail.prev = this.head;
}
// 2.2 添加到头
public void addHead(Node<K, V> node) {
node.next = head.next;
node.prev = head;
head.next.prev = node;
head.next = node;
}
//2.3 删除节点
public void removeNode(Node<K, V> node) {
node.next.prev = node.prev;
node.prev.next = node.next;
node.prev = null;
node.next = null;
}
//2.4 获得最后一个节点
public Node<K, V> getLast() {
return tail.prev;
}
}
private int cacheSize;
private Map<Integer, Node<Integer, Integer>> map;
private DoubleLinkList<Integer, Integer> doubleLinkList;
public Q149_1(int cacheSize) {
this.cacheSize = cacheSize;
this.map = new HashMap<>();
doubleLinkList = new DoubleLinkList<>();
}
public int get(int key) {
if (!map.containsKey(key)) {
return -1;
}
Node<Integer, Integer> node = map.get(key);
doubleLinkList.removeNode(node);
doubleLinkList.addHead(node);
return node.value;
}
// save of update method
public void put(int key, int value) {
// update
if (map.containsKey(key)) {
Node<Integer, Integer> node = map.get(key);
node.value = value;
map.put(key, node);
doubleLinkList.removeNode(node);
doubleLinkList.addHead(node);
} else {
if (map.size() == cacheSize) {
Node<Integer, Integer> lastNode = doubleLinkList.getLast();
map.remove(lastNode.key);
doubleLinkList.removeNode(lastNode);
}
// 新增
Node<Integer, Integer> newNode = new Node<>(key, value);
map.put(key, newNode);
doubleLinkList.addHead(newNode);
}
}
public static void main(String[] args) {
Q149_1 q149 = new Q149_1(3);
q149.put(1, 1);
q149.put(2, 2);
q149.put(3, 3);
System.out.println(q149.map.keySet());
q149.put(1, 2);
q149.put(2, 1);
System.out.println(q149.map.keySet());
}
}
参考资料
转载自:https://juejin.cn/post/7064421253167185957