最近最久未使用(Least Recently Used LRU)算法是⼀种缓存淘汰策略,它是大部分操作系统为最大化页面命中率而广泛采用的一种页面置换算法。该算法的思路是,发生缺页中断时,将最近一段时间内最久未使用的页面置换出去。 从程序运行的原理来看,最近最久未使用算法是比较接近理想的一种页面置换算法,这种算法既充分利用了内存中页面调用的历史信息,又正确反映了程序的局部问题。
虚拟页式存储管理,则是将进程所需空间划分为多个页面,内存中只存放当前所需页面,其余页面放入外存的管理方式。
有利就有弊,虚拟页式存储管理减少了进程所需的内存空间,却也带来了运行时间变长这一缺点:进程运行过程中,不可避免地要把在外存中存放的一些信息和内存中已有的进行交换,由于外存的低速,这一步骤所花费的时间不可忽略。因而,采取尽量好的交换算法以减少读取外存的次数,也是相当有意义的事情。
假设 序列为 4 3 4 2 3 1 4 2
物理块有3个 则
4调入内存 4
3调入内存 3 4
4调入内存 4 3
2调入内存 2 4 3
3调入内存 3 2 4
1调入内存 1 3 2(因为最少使用的是4,所以丢弃4)
4调入内存 4 1 3(原理同上)
2调入内存 2 4 1
规律就是,如果新存入或者访问一个值,则将这个值放在队列开头。如果存储容量超过上限cap,那么删除队尾元素,再存入新的值。
我们下面通过一个简单的存储int的方式来实现LRU cache,实现put和get功能。
⾸先要接收⼀个参数作为缓存的最⼤容量, 然后实现两个 API, ⼀个是 put(key, val) ⽅法存⼊键值对,get(key) ⽅法获取 key 对应的 val, 如果 key 不存在则返回null,get 和 put ⽅法必须都是 O(1) 的时间复杂度。
因此LRU cache 的数据结构的必要的条件: 查找快, 插⼊快, 删除快, 有顺序之分。那么, 什么数据结构同时符合上述条件呢? 哈希表查找快, 但是数据⽆固定顺序; 链表有顺序之分, 插⼊删除快, 但是查找慢。 所以结合⼀下, 形成⼀种新的数据结构: 哈希链表。如下图所示:
新插入的元素或者最新查询的元素要放到链表的头部,对于长时间未访问的元素要放到链表尾部,所以每次插入或者查询都需要维护链表中元素的顺序。
使用哈希表的原因是查询时间复杂度为O(1),使用双向链表的原因是对于删除和插入操作时间复杂度为O(1)。
其中哈希表中存储的 key 为 K,value 为 Node
//通过与链表配合,使其查找速度在O(1)下完成
private Map> map;
//链表优点:便于添加和删除元素,都在O(1)时间内完成
private LinkedList> linkedList;
Node
private static class Node{
K key;
V value;
public Node(K key, V value) {
this.key = key;
this.value = value;
}
@Override
public String toString() {
return "(" +
"key=" + key +
", value=" + value +
')';
}
}
put 方法是线程安全方法,定义如下所示:
public synchronized void put(K key,V value)
对于put操作:
①首先判断缓存中 元素 K 是否存在,如果存在,则把链表中的元素Node
②缓存不存在,并且缓存没有满的话,直接把元素插入链表的表头,缓存满了的话移除表尾元素(最旧未访问元素),将元素K插入表头,增加map中的
get 方法是线程安全方法,定义如下所示:
public synchronized V get(K key)
对于get操作:
首先要判断 缓存中(map)是否存在,如果存在则把该节点删除并在链表头部插入该元素并更新map 返回当前元素即可,如果map不存在 则直接返回null;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.Map;
import java.util.Objects;
public class LruCache {
private Map> map;
private LinkedList> linkedList;
private int size;
private int defaultSize = 16;
public LruCache() {
this(16);
}
public LruCache(int size) {
if(size <= 0){
this.size = 16;
}else {
this.size = size;
}
this.map = new HashMap<>(this.size);
this.linkedList = new LinkedList<>();
}
public synchronized void put(K key,V value){
//待添加元素
Node insertNode = new Node<>(key, value);
//判断元素是否存在
Node node = map.get(key);
//如果存在,则把链表中的元素删除,map中的数据不用删除,再在链表头部插入元素
if(Objects.nonNull(node)){
linkedList.remove(node);
linkedList.addFirst(insertNode);
//更新map中的相应value
map.put(key,insertNode);
return;
}
//如果缓存已经满的话,则删除最后未访问元素
if(this.size == map.size()){
//直接删除队列尾部元素,同时更新map
linkedList.removeLast();
map.remove(key);
//在队列头部插入元素,同时更新map
linkedList.addFirst(insertNode);
map.put(key,insertNode);
return;
}
//如果缓存没满,则执行添加即可
map.put(key,insertNode);
linkedList.addFirst(insertNode);
}
public synchronized V get(K key){
//先向map查询是否存在
Node node = map.get(key);
if(Objects.isNull(node)){
return null;
}
//如果存在,则先删除后添加到队列头
linkedList.remove(node);
linkedList.addFirst(node);
return node.value;
}
@Override
public String toString() {
StringBuilder stringBuilder = new StringBuilder();
for(Node node : linkedList ){
stringBuilder.append(node.toString()).append(",");
}
return stringBuilder.toString().substring(0,stringBuilder.length()-1);
}
//根据key删除缓存元素
private void delete(K key){
//判断是否存在相应元素
Node node = map.get(key);
//如果元素进行存在则进行删除
if(Objects.nonNull(node)){
linkedList.remove(node);
}
}
private static class Node{
K key;
V value;
public Node(K key, V value) {
this.key = key;
this.value = value;
}
@Override
public String toString() {
return "(" +
"key=" + key +
", value=" + value +
')';
}
}
}
LruCacheTest.java 测试类内容如下所示:
public class LruCacheTest {
public static void main(String[] args){
LruCache lruCache = new LruCache<>(3);
lruCache.put("one","one");
lruCache.put("two","two");
lruCache.put("three","three");
System.out.println(lruCache.toString());
lruCache.get("one");
System.out.println(lruCache.toString());
lruCache.put("four","four");
System.out.println(lruCache.toString());
lruCache.get("one");
System.out.println(lruCache.toString());
System.out.println(lruCache.get("five"));
lruCache.put("four","five");
System.out.println(lruCache.toString());
}
}
结果: