前言
在操作系统中,页面置换算法中有一种思想叫做LRU,就是选择最近最少使用的页将其置换出去。LRU是一种缓存淘汰策略,那么在Java中结合学习过的基础数据机构如何能实现LRU效果呢?
解决思路
首先要知道Java的内置容器LinkedHashMap已经可以实现LRU缓存,具体做法如下:
class LRUCache<K, V> extends LinkedHashMap<K, V> {
private static final int MAX_ENTRIES = 3;
protected boolean removeEldestEntry(Map.Entry eldest) {
return size() > MAX_ENTRIES;
}
LRUCache() {
super(MAX_ENTRIES, 0.75f, true);
}
}
去查看源码可以发现jdk内部维护了一个双向链表,用来维护插入顺序或者 LRU 顺序,继承自 HashMap,因此具有和 HashMap 一样的快速查找特性。
由此启发,可以使用map + 双向链表的思想来实现LRU缓存。
具体实现
双向链表节点比单向链表接节点多了一个指向前驱节点的指针,使用双向链表的目的是为了能在O(1)时间里删除一个节点、删除末尾节点。
链表节点定义如下:
class MyNode{
public int key, value;
public MyNode next, pre;
public MyNode(int key, int value){
this.key = key;
this.value = value;
}
}
双向链表需要实现往头部插入一个节点、删除一个节点、删除尾部节点并返回这几个操作。为了在常数级时间里完成这三个操作,需要维护头指针和尾指针分别指向链表的头和尾。
class DoubleList{
public MyNode head, tail;
public int size;
public void addFirst(MyNode node){
if (head == null){ //链表为空时
head = tail = node;
}else {
MyNode cur = head;
cur.pre = node;
node.next = cur;
head = node;
}
size++; //每添加一个节点,链表长度加一
}
public void remove(MyNode node){
if (head == node && tail == node){ //链表只有一个节点时或者链表为空时
head = null;
tail = null;
}else if (tail == node){ //当要删除的节点是尾节点时
node.pre.next = null;
tail = node.pre; //更新尾节点
}else if (head == node){ //要删除的节点是头节点时
node.next.pre = null;
head = node.next; //更新头节点
}else { //删除节点位于链表的中间位置
node.pre.next = node.next;
node.next.pre = node.pre;
}
size--; //删除节点,长度减一
}
public MyNode removeLast(){
MyNode node = tail; //记录当前尾节点
remove(tail);//删除节点
return node; //返回尾节点
}
}
下面就要实现LRU缓存,map可以保证查找效率为常数级,链表能保证删除时间复杂度为常数级。
public class LRUCache146{
private int capacity;
private HashMap<Integer, MyNode> map = new HashMap<>();
private DoubleList list;
public LRUCache146(int capacity){
this.capacity = capacity;
this.list = new DoubleList();
}
public int get(int key) {
if (!map.containsKey(key))
return -1;
int val = map.get(key).value;
put(key, val);
return val;
}
public void put(int key, int value){
MyNode cur = new MyNode(key, value);
if (map.containsKey(key)){
list.remove(map.get(key));
}else {
if (capacity == list.size){
MyNode last = list.removeLast();
map.remove(last.key);
}
}
list.addFirst(cur);
map.put(key, cur);
}
}
上面的代码和思路主要借鉴了LRU 策略详解和实现 这篇文章。