什么是LRU算法?
百度百科:
LRU是Least Recently Used的缩写,即最近最少使用,是一种常用的页面置换算法,选择最近最久未使用的页面予以淘汰。该算法赋予每个页面一个访问字段,用来记录一个页面自上次被访问以来所经历的时间 t,当须淘汰一个页面时,选择现有页面中其 t 值最大的,即最近最少使用的页面予以淘汰。
总结来说就是末尾淘汰!目前根据自己的知识储备lru算法运用于redis缓存淘汰策略,mysql的缓存清理策略
也是根据最近刷LeeCode看到的一套题
https://leetcode-cn.com/problems/lru-cache/
如何实现?
1、通过LinkedHashMap实现
1)代码+测试脚本(有点low)
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Scanner;
class LRUCache {
private final Map<Integer, Integer> cacheMap;
public LRUCache(int capacity) {
int length = (int) Math.ceil(capacity / 0.75);
cacheMap = new LinkedHashMap<Integer, Integer>(length, 0.75f, true) {
@Override
protected boolean removeEldestEntry(Map.Entry<Integer, Integer> eldest) {
return cacheMap.size() > capacity;
}
};
}
public int get(int key) {
return cacheMap.getOrDefault(key, -1);
}
public void put(int key, int value) {
cacheMap.put(key, value);
}
public static void main(String[] args) {
int length, key;
Scanner sc = new Scanner(System.in);
System.out.print("length=");
length = sc.nextInt();
System.out.println();
LRUCache cache = new LRUCache(length);
initMap(sc, cache);
while (true) {
System.out.println();
System.out.print("get=");
key = sc.nextInt();
if (key == 0) {
break;
}
int i = cache.get(key);
System.out.println();
System.out.println("key=" + key + ",value=" + i);
}
initMap(sc, cache);
System.out.println("=====");
cache.cacheMap.forEach((k, v) -> System.out.println(k + ":" + v));
}
private static void initMap(Scanner sc, LRUCache cache) {
int key;
int value;
while (true) {
System.out.print("key=");
key = sc.nextInt();
if (key == 0) {
break;
}
System.out.println();
System.out.print("value=");
value = sc.nextInt();
cache.put(key, value);
}
}
}
运行结果:
length=2
key=1
value=1
key=2
value=2
key=0
get=1
key=1,value=1
get=0
key=3
value=3
key=0
=====
1:1
3:3
Process finished with exit code 0
长度为2 分别输入[1,1] [2,2] 输入0结束,查询key=1 输入0结束,再存入[3,3] 输入0结束 最终打印[1,1][3,3]
因为会涉及到扩容问题,所以在初始化的时候根据加载因子先根据输入的长度计算map大小再初始化
复杂度分析
时间复杂度:对于 put 和 get 操作复杂度是 O(1)O(1),因为有序字典中的所有操作:get/in/set/move_to_end/popitem(get/containsKey/put/remove)都可以在常数时间内完成。
空间复杂度:O(capacity)O(capacity),因为空间只用于有序字典存储最多 capacity + 1 个元素。
方法 2:哈希表 + 双向链表
这个问题可以用哈希表,辅以双向链表记录键值对的信息。所以可以在 O(1)O(1) 时间内完成 put 和 get 操作,同时也支持 O(1)O(1) 删除第一个添加的节点。
使用双向链表的一个好处是不需要额外信息删除一个节点,同时可以在常数时间内从头部或尾部插入删除节点。
一个需要注意的是,在双向链表实现中,这里使用一个伪头部和伪尾部标记界限,这样在更新的时候就不需要检查是否是 null 节点。
实现
class LRUCache extends LinkedHashMap<Integer, Integer>{
private int capacity;
public LRUCache(int capacity) {
super(capacity, 0.75F, true);
this.capacity = capacity;
}
public int get(int key) {
return super.getOrDefault(key, -1);
}
public void put(int key, int value) {
super.put(key, value);
}
@Override
protected boolean removeEldestEntry(Map.Entry<Integer, Integer> eldest) {
return size() > capacity;
}
}
复杂度分析
时间复杂度:对于 put 和 get 都是 O(1)O(1)。
空间复杂度:O(capacity)O(capacity),因为哈希表和双向链表最多存储 capacity + 1 个元素。