LRU缓存设计
什么是LRU
- 概念
- LRU的初始设计与应用是在操作系统中的虚拟存储模块上,它作为操作系统的虚拟页面置换的算法,得到了广泛的应用。
- 特点与应用
- 由于操作系统的页表大小有限,页面项的数量也有限,当我们访问某个物理块的时候,可能产生缺页中断,这个时候操作系统就会触发调页请求,从外存置换对应的物理页进入内存,提供访问。而当页表已经存满对应的物理页映射项的情况下,仍然发生中断请求,这个时候就需要一种页面置换算法,将外存的物理页换入内存,淘汰久的页面。
- LRU利用程序的局部性原理,对最近访问的页面进行更新,淘汰最久未使用的页面。
- 应用场景
- LRU的置换思想,同样可以应用到缓存的技术中。接下来,我们将设计一个高性能的LRU缓存。
LRU缓存设计
- 参数
- Size缓存大小
- 是一种K-V结构
- get(K key)和put(K key,V val)
- 性能要求
- O(1)的读写效率
设计步骤
- 选择合适的数据结构
首先我们进行了分析,K-V结构最常用的就是我们的hashMap,用hashMap存取数据,可以达到O(1)。
- 存在问题
- 用hashMap解决O(1)的读写效率,但是如何能够用O(1)进行置换呢?
- 分析
- 读可以用O(1)解决,但是写不行,在容量已满的情况下,写缓存必然导致数据的置换,如何能够用O(1)进行数据置换呢?
- 解决方案
- 选择哈希双链表数据结构,如下:
代码实现
public class LRUCache<K, V> {
private int size;
/**
* virtual nodes
*/
private Node tail, head;
private int capacity;
private final Map<K, Node> cache = new HashMap<>();
public LRUCache(int capacity) {
if (capacity < 1) {
throw new IllegalArgumentException("capacity can not be < 1");
}
tail = new Node(null, null);
head = new Node(null, null);
head.next = tail;
tail.prev = head;
this.size = 0;
this.capacity = capacity;
}
public V get(K key) {
if (!cache.containsKey(key)) {
return null;
} else {
V val = cache.get(key).val;
//调用put更新即可
put(key, val);
return val;
}
}
public void put(K key, V val) {
//构造新节点
Node newNode = new Node(key, val);
if (cache.containsKey(key)) {
//remove old
remove(cache.get(key));
//add new
addFirst(newNode);
//更新map
cache.put(key, newNode);
} else {
if (capacity == size()) {
Node last = removeLast();
cache.remove(last.key);
}
//添加到头部
addFirst(newNode);
cache.put(key, newNode);
}
}
private void addFirst(Node node) {
if (node != null) {
node.next = head.next;
node.prev = head;
head.next.prev = node;
head.next = node;
size++;
}
}
private void remove(Node node) {
if (node != null) {
node.prev.next = node.next;
node.next.prev = node.prev;
size--;
}
}
private Node removeLast() {
if (tail.prev == head) {
return null;
}
Node last = tail.prev;
remove(last);
return last;
}
public int size() {
return size;
}
public void print() {
for (Map.Entry e : cache.entrySet()
) {
System.out.println(e.getKey() + ":" + ((Node) e.getValue()).val);
}
}
class Node {
K key;
V val;
Node next, prev;
public Node(K key, V val) {
this.key = key;
this.val = val;
}
}
}