LRU介绍
- LRU是Least Recently Used的缩写,即最近最少使用,是一种常用的页面置换算法,选择最近最久未使用的页面予以淘汰。该算法赋予每个页面一个访问字段,用来记录一个页面自上次被访问以来所经历的时间 t,当须淘汰一个页面时,选择现有页面中其 t 值最大的,即最近最少使用的页面予以淘汰。——《百科词条》
- LRU是常见算法,详情可查阅相关算法资料,本文不再赘述
题目
实现一个LRU缓存,要求:获取、存储缓存操作的时间复杂度为O(1)
分析
- 存储key、value:使用哈希表
- 使用链表
- 数据有先后顺序
- O(1)时间复杂度内调整节点顺序
- 获取缓存,未命中:返回-1
- 获取缓存,命中:返回缓存并将缓存移至链表头部
- 写入缓存,已存在:更新缓存并将缓存移至链表头部
- 写入缓存,不存在:写入缓存(在链表头部)
- 写入缓存,超出缓存容量:删除最久未使用的缓存
题解
public class LRUCache {
int capacity, size;
// 1. 存储key、value:使用哈希表
Map<Integer, DoubleLinkedNode> map = new HashMap<>();
// 2. 使用链表
DoubleLinkedNode dummyHead, dummyTail;
public LRUCache(int capacity) {
this.capacity = capacity;
dummyHead = new DoubleLinkedNode();
dummyTail = new DoubleLinkedNode();
dummyHead.next = dummyTail;
dummyTail.pre = dummyHead;
}
/**
* 获取缓存
*
* @param key
* @return
*/
public int get(int key) {
// 3. 获取缓存,未命中:返回-1
if (!map.containsKey(key)) {
return -1;
}
// 4. 获取缓存,命中:返回缓存并将缓存移至链表头部
DoubleLinkedNode node = map.get(key);
moveToHead(key, node);
return node.value;
}
/**
* 写入缓存
*
* @param key
* @param value
*/
public void put(int key, int value) {
if (map.containsKey(key)) {
// 5. 写入缓存,已存在:更新缓存并将缓存移至链表头部
DoubleLinkedNode node = map.get(key);
node.value = value;
moveToHead(key, node);
} else {
// 6. 写入缓存,不存在:写入缓存(在链表头部)
DoubleLinkedNode node = new DoubleLinkedNode(key, value);
add(key, node);
// 7. 写入缓存,超出缓存容量:删除最久未使用的缓存
if (size > capacity) {
DoubleLinkedNode tail = dummyTail.pre;
remove(tail);
}
}
}
/**
* 将节点移至头部
*
* @param key
* @param node
*/
private void moveToHead(int key, DoubleLinkedNode node) {
remove(node);
add(key, node);
}
/**
* 添加节点
*
* @param key
* @param node
*/
private void add(int key, DoubleLinkedNode node) {
// 添加到链表头部
node.pre = dummyHead;
node.next = dummyHead.next;
dummyHead.next.pre = node;
dummyHead.next = node;
// 添加到map中
map.put(key, node);
size++;
}
/**
* 删除node
*
* @param node
*/
private void remove(DoubleLinkedNode node) {
// 从链表中删除
node.pre.next = node.next;
node.next.pre = node.pre;
// 从map中删除
map.remove(node.key);
size--;
}
/** 定义双链表节点 */
class DoubleLinkedNode {
int key, value;
// 前一个节点
DoubleLinkedNode pre;
// 后一个节点
DoubleLinkedNode next;
DoubleLinkedNode() {}
DoubleLinkedNode(int key, int value) {
this.key = key;
this.value = value;
}
}
}
写在最后
工作中可使用Java的LinkedHashMap,一个简单示例
public class LRUCache extends LinkedHashMap<Integer, Integer> {
private int capacity;
public LRUCache(int capacity) {
super(capacity, 0.8F, true);
this.capacity = capacity;
}
/**
* 获取缓存
*
* @param key
* @return
*/
public int get(int key) {
return super.getOrDefault(key, -1);
}
/**
* 写入缓存
*
* @param key
* @param value
*/
public void put(int key, int value) {
super.put(key, value);
}
/**
* 超出缓存容量,删除最久未使用的缓存
*
* @param eldest
* @return
*/
@Override
protected boolean removeEldestEntry(Map.Entry<Integer, Integer> eldest) {
return size() > this.capacity;
}
}