缓存淘汰算法–LRU算法的实现
- LRU
- Least recently used, 最近最少使用
- 根据数据的历史访问记录来进行淘汰数据
- 其核心思想是”如果数据最近被访问过,那么将来被访问的几率也更高”
- 实现保存缓存数据
- 循环数组实现的队列
- 数字在小范围的遍历很快
- 队列实现LRU
- 链表
- 链表的插入和删除时间复杂度都为O(1)–>用链表不用数组
- 具体过程
- 新数据插入到链表头部
- 每当缓存命中(即缓存数据被访问),则将数据移到链表头部
- 当链表满的时候,将链表尾部的数据丢弃
- 分析
- 【命中率】当存在热点数据时,LRU的效率很好,但偶发性的、周期性的批量操作会导致LRU命中率急剧下降,缓存污染情况比较严重
- 【复杂度】实现简单
- 【代价】 命中时需要遍历链表,找到命中的数据块索引,然后需要将数据移到头部
- LinkedHashMap
- 利用hashmap的特点–命中很快(是否在缓存中)
- 双向链表的队列实现LRU
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39import java.util.*;
class LRULinkedHashMap<K,V> extends LinkedHashMap<K,V> {
//定义缓存的容量--LinkedHashMap默认超过最大容量自动扩容,不存在remove eldest的问题
private int capacity;
private static final long serialVersionUID = 1L;
//带参数的构造器
LRULinkedHashMap(int capacity){
//调用LinkedHashMap的构造器,传入以下参数
super(16,0.75f,true);
//传入指定的缓存最大容量
this.capacity=capacity;
}
//实现LRU的关键方法,如果map里面的元素个数大于了缓存最大容量,则删除链表的顶端元素
public boolean removeEldestEntry(Map.Entry<K, V> eldest){
// 每次增加都会调用该方法看是否需要remove eldest
System.out.println(eldest.getKey() + "=" + eldest.getValue());
return size()>capacity;
/*return false;*/
}
}
//测试类
public class Main{
public static void main(String[] args) throws Exception{
//指定缓存最大容量为4
Map<Integer,Integer> map=new LRULinkedHashMap<>(4);
map.put(9,3);
map.put(7,4);
map.put(5,9);
map.put(3,4);
//map.put(6,6);
//总共put了5个元素,超过了指定的缓存最大容量
//遍历结果
for(Iterator<Map.Entry<Integer,Integer>> it = map.entrySet().iterator(); it.hasNext();){
System.out.println(it.next().getKey());
}
}
}
- 循环数组实现的队列