借助LinkedHashMap实现基于LRU算法缓存
一、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 }
内存情况如下(最左端为最常访问的):
申请空间:
map.put(2,2);
map.put(3,3);
map.put(5,5);
map.get(2);
map.put(7,7);
map.put(11,11);
所以最后打印出来的就是2 7 11。