一、LRU算法介绍
LRU(Least Recently Used)最近最少使用算法,是用在操作系统中的页面置换算法,因为内存空间是有限的,不可能把所有东西都放进来,所以就必须要有所取舍,我们应该把什么东西放进来呢?有没有什么判定标准呢?页面置换算法就是干这个的,企图通过之前的行为预测到之后的行为(这是概率问题),而LRU就是其中的一种,它的基本思想就是既然有一块数据,最近的一段时间内它是最少访问的,这说明在这之后它也可能是最少访问的,如果非要移除一个的话,我只好把它置换出内存了。
总结一下:
LRU:在空间有限的情况下必须做出取舍,将最可能最少被访问的数据置换掉。
二、为什么使用LRU
QUESTION: 我用HashMap也挺好的啊,为什么要使用缓存昂?!
用数据说话,做一个实验来说明为什么要使用缓存,new两个Map底层分别使用HashMap和LinkedHashMap,然后往里面装一百万条数据,然后分别总是访问同一条数据十万次和随机访问十万次:
1 import java.util.HashMap;
2 import java.util.LinkedHashMap;
3 import java.util.Map;
4 import java.util.Random;
5
6 public class Main {
7
8 public static void main(String[] args) {
9
10 Map<Integer,Integer> map1=new LinkedHashMap<Integer,Integer>(16,0.75F,true);
11 Map<Integer,Integer> map2=new HashMap<Integer,Integer>();
12
13 for(int i=0;i<1000000;i++){
14 map1.put(i,i);
15 map2.put(i,i);
16 }
17
18 // 大量访问同一条数据
19 long start=System.currentTimeMillis();
20 for(int i=0;i<1000000000;i++){
21 map1.get(1000000);
22 }
23 System.out.printf("HashMap: %dms\n",System.currentTimeMillis()-start);
24
25 start=System.currentTimeMillis();
26 for(int i=0;i<100000;i++){
27 map2.get(999);
28 }
29 System.out.printf("LinkedHashMap: %dms\n",System.currentTimeMillis()-start);
30
31
32 //随机访问数据
33 start=System.currentTimeMillis();
34 for(int i=0;i<1000000000;i++){
35 map1.get(1000000);
36 }
37 System.out.printf("HashMap: %dms\n",System.currentTimeMillis()-start);
38
39 start=System.currentTimeMillis();
40 for(int i=0;i<100000;i++){
41 map2.get(new Random().nextInt(1000000));
42 }
43 System.out.printf("LinkedHashMap: %dms\n",System.currentTimeMillis()-start);
44
45 }
46
47 }
结果如下:
我们打一个表格以便于分析:
首先进行横向比较,无论怎么访问LinkedHashMap总是要比HashMap快得多,
然后进行纵向比较发现HashMap访问同一条数据和随机访问花费的时间差不多,因为它是无状态的,也就是它并不能根据之前的结果帮助到以后的查询,LinkedHashMap的随机访问明显要慢一些了,LRU的策略主要就是用于短时间内频繁的访问相同的数据的。
三、如何使用LinkedHashMap实现LRUMap
继承LinkedHashMap然后覆写removeEldestEntry方法即可,这个方法在每次添加元素之前调用来判断是否需要删除元素,如果需要的话就会用LRU算法移除掉最近最少使用的元素,如下:
1 import java.util.Iterator;
2 import java.util.LinkedHashMap;
3 import java.util.Map;
4 import java.util.Map.Entry;
5
6 public class Main {
7
8 public static void main(String[] args) {
9
10 Map<Integer,Integer> map=new LRUMap<>(3);
11
12 map.put(2,2);
13 map.put(3,3);
14 map.put(5,5);
15 map.get(2);
16 map.put(7,7);
17 map.put(11,11);
18
19 for(Iterator<Entry<Integer,Integer>> iter=map.entrySet().iterator();iter.hasNext();){
20 System.out.print(iter.next().getKey()+" "); //2 7 11
21 }
22
23 }
24
25 }
26
27 class LRUMap<K,V> extends LinkedHashMap<K,V>{
28
29 private int capacity;
30
31 public LRUMap() {
32 this(16);
33 }
34
35 public LRUMap(int capacity) {
36 super(capacity,0.75F,true);
37 this.capacity=capacity;
38 }
39
40 @Override
41 protected boolean removeEldestEntry(java.util.Map.Entry<K, V> eldest) {
42 return size()>capacity;
43 }
44
45 }
内存情况如下(最左端为最常访问的):
申请空间:
2,2);
3,3);
map.put(5,5);
map.get(2);
map.put(7,7);
map.put(11,11);
所以最后打印出来的就是2 7 11。