LRU算法与LRUCache

  关于LRU


 

  LRU(Least recently used,最近最少使用)算法是操作系统中一种经典的页面置换算法,当发生缺页中断时,需要将内存的一个或几个页面置换出,LRU指出应该将内存最近最少使用的那些页面换出,依据的是程序的局部性原理,最近经常使用的页面再不久的将来也很有可能被使用,反之最近很少使用的页面未来也不太可能在使用。

  其核心思想是“如果数据最近被访问过,那么将来被访问的几率也更高”。但此算法不能保证过去不常用,将来也不常用。

  设计目标


 

  1、实现LRU算法。

  2、学以致用,了解算法实际应用场景。

  3、封装LRUCache数据结构。

  4、实现线程安全与线程不安全两种版本LRUCache。


 实际应用LRU


   LRU算法非常实用,不仅在操作系统中发挥着很大作用,而且他还是一款缓存淘汰算法。

  在做大型软件或网站服务时,如果想要让系统稳定并且能够承受得住千万级用户的高并发访问,就要尽量缩短因日常维护操作(计划)和突发的系统崩溃(非计划)所导致的停机时间,以提高系统和应用的可用性。那么我们必然要采取一些高可用的措施。

  有人说互联网用户是用脚投票的,这句话其实也从侧面说明了,用户体验是多么的重要。这就要求在软件架构设计时,不但要注重可靠性、安全性、可扩展性以及可维护性等等的一些指标,更要注重用户的体验,用户体验分很多方面,但是有一点非常重要就是对用户操作的响应一定要快。怎样提高用户访问的响应速度,这就是摆在架构设计中必须要解决的问题。说道提高服务的响应速度就不得不说缓存了。

  缓存有三种:数据库缓存、静态缓存和动态缓存。

  从系统的层面说,CPU的速度远远高于磁盘IO的速度。所以要想提高响应速度,必须减少磁盘IO的操作,但是有很多信息又是存在数据库当中的,每次查询数据库就是一次IO操作。

  在目前主流的memcache和redis中都有LRU算法的身影。在两大中间件中,LRU算法都在他们之中起到缓存回收的作用。关于他们的源码以后打算分析。

  静态缓存:一般指 web 类应用中,将图片、js、css、视频、html等静态文件/资源通过磁盘/内存等缓存方式,提高资源响应方式,减少服务器压力/资源开销的一门缓存技术。静态缓存技术:CDN是经典代表之作。静态缓存技术面非常广,涉及的开源技术包含apache、Lighttpd、nginx、varnish、squid等。

  动态缓存:用于临时文件交换,缓存是指临时文件交换区,电脑把最常用的文件从存储器里提出来临时放在缓存里,就像把工具和材料搬上工作台一样,这样会比用时现去仓库取更方便。

  LRU算法过程


     链表+容器实现LRU缓存

   传统意义的LRU算法是为每一个Cache对象设置一个计数器,每次Cache命中则给计数器+1,而Cache用完,需要淘汰旧内容,放置新内容时,就查看所有的计数器,并将最少使用的内容替换掉。

  它的弊端很明显,如果Cache的数量少,问题不会很大, 但是如果Cache的空间过大,达到10W或者100W以上,一旦需要淘汰,则需要遍历所有计算器,其性能与资源消耗是巨大的。

  效率也就非常的慢了。

  所以采用双向链表+hash表的数据结构实现,双向链表作为队列存储当前缓存节点,其中从表头到表尾的元素按照最近使用的时间进行排列,放在表头的是最近刚刚被使用过的元素,表尾的最近最少使用的元素;如果仅仅采用双向链表,那么查询某个元素需要 O(n) 的时间,为了加快双向链表中元素的查询速度,采用hash表讲key进行映射,可以在O(1)的时间内找到需要节点。

  

  1. 新数据插入到链表头部;

  2. 每当缓存命中(即缓存数据被访问),则将数据移到链表头部;

  3. 当链表满的时候,将链表尾部的数据丢弃。

  【命中率】 

  命中率=命中数/(命中数+没有命中数), 缓存命中率是判断加速效果好坏的重要因素之一。

  当存在热点数据的时候,LRU效率很好,但偶发性、周期性的批量操作会导致LRU命中率急剧下滑,缓存污染的情况比较严重。  

   

    

  

  原理: 将Cache的所有位置都用双连表连接起来,当一个位置被命中之后,就将通过调整链表的指向,将该位置调整到链表头的位置,新加入的Cache直接加到链表头中。 
这样,在多次进行Cache操作后,最近被命中的,就会被向链表头方向移动,而没有命中的,而想链表后面移动,链表尾则表示最近最少使用的Cache。 
当需要替换内容时候,链表的最后位置就是最少被命中的位置,我们只需要淘汰链表最后的部分即可。

  

  1 package com.zuo.lru;
  2 
  3 import java.util.HashMap;
  4 
  5 /**
  6  * 
  7  * @author zuo
  8  *    线程不安全
  9  * @param <K>
 10  * @param <V>
 11  */
 12 public class LRUCache<K, V> {
 13 
 14     private int currentCacheSize;    //当前缓存大小
 15     private int CacheCapcity;        //缓存上限
 16     private HashMap<K, CacheNode> caches; //缓存表
 17     private CacheNode first;    
 18     private CacheNode last;
 19     
 20     public LRUCache(int size) {
 21         currentCacheSize=0;
 22         this.CacheCapcity=size;
 23         caches=new HashMap<K,CacheNode>(size);
 24     }
 25     
 26     /**
 27      * 添加
 28      * @param k
 29      * @param v
 30      */
 31     public void put(K k,V v){
 32         CacheNode node=caches.get(k);
 33         if(node==null){
 34             if(caches.size()>=CacheCapcity){
 35                 caches.remove(last.key);
 36                 removeLast();
 37             }
 38             node=new CacheNode();
 39             node.key=k;
 40         }
 41         node.value=v;
 42         moveToFirst(node);
 43         caches.put(k, node);
 44     }
 45     
 46     public Object get(K k){
 47         CacheNode node=caches.get(k);
 48         if(node==null){
 49             return null;
 50         }
 51         moveToFirst(node);
 52         return node.value;
 53     }
 54     
 55     /**
 56      * 删除
 57      * @param k
 58      * @return
 59      */
 60     public Object remove(K k){
 61         CacheNode node=caches.get(k);
 62         if(node!=null){
 63             if(node.pre!=null){
 64                 node.pre.next=node.next;//前结点的后指针指向当前节点的下一个
 65             }
 66             if(node.next!=null){
 67                 node.next.pre=node.pre;//后节点的前指针指向当前结点的上一个
 68             }
 69             if(node==first){
 70                 first=node.next;
 71             }
 72             if(node==last){
 73                 last=node.pre;
 74             }
 75         }
 76         return caches.remove(k);
 77     }
 78     
 79     /**
 80      * 删除last
 81      */
 82     private void removeLast(){
 83         if(last!=null){
 84             last=last.pre;
 85             if(last==null){
 86                 first=null;
 87             }else{
 88                 last.next=null;
 89             }
 90         }
 91     }
 92     
 93     /**
 94      * 将node移动到头说明使用频率高
 95      * @param node
 96      */
 97     private void moveToFirst(CacheNode node){
 98         if(first==node){
 99             return;
100         }
101         if(node.pre!=null){
102             node.pre.next=node.next;//前结点的后指针指向当前节点的下一个
103         }
104         if(node.next!=null){
105             node.next.pre=node.pre;//后节点的前指针指向当前结点的上一个
106         }
107         if(node==last){
108             last=last.pre;
109         }
110         if(first==null || last==null){
111             first=last=node;
112             return;
113         }
114         node.next=first;
115         first.pre=node;
116         first=node;
117         first.pre=null;
118     }
119     
120     
121     
122     /**
123      * 清空
124      */
125     public void clear(){
126         first=null;
127         last=null;
128         caches.clear();
129     }
130     
131     @Override
132     public String toString() {
133         StringBuilder stringBuilder=new StringBuilder();
134         CacheNode node=first;
135         while(node!=null){
136             stringBuilder.append(String.format("%s:%s ", node.key,node.value));
137             node=node.next;
138         }
139         return stringBuilder.toString();
140     }
141     
142     /**
143      * @author zuo
144      * 双向链表
145      */
146     class CacheNode{
147         CacheNode pre; //前指针
148         CacheNode next;//后指针
149         Object key;    //
150         Object value;  //
151         public CacheNode() {
152         }
153     }
154     
155     public int getCurrentCacheSize() {
156         return currentCacheSize;
157     }
158     
159 
160     public static void main(String[] args) {
161 
162         LRUCache<Integer,String> lru = new LRUCache<Integer,String>(3);
163 
164         lru.put(1, "a");    // 1:a
165         System.out.println(lru.toString());
166         lru.put(2, "b");    // 2:b 1:a 
167         System.out.println(lru.toString());
168         lru.put(3, "c");    // 3:c 2:b 1:a 
169         System.out.println(lru.toString());
170         lru.put(4, "d");    // 4:d 3:c 2:b  
171         System.out.println(lru.toString());
172         lru.put(1, "aa");   // 1:aa 4:d 3:c  
173         System.out.println(lru.toString());
174         lru.put(2, "bb");   // 2:bb 1:aa 4:d
175         System.out.println(lru.toString());
176         lru.put(5, "e");    // 5:e 2:bb 1:aa
177         System.out.println(lru.toString());
178         lru.get(1);         // 1:aa 5:e 2:bb
179         System.out.println(lru.toString());
180         lru.remove(11);     // 1:aa 5:e 2:bb
181         System.out.println(lru.toString());
182         lru.remove(1);      //5:e 2:bb
183         System.out.println(lru.toString());
184         lru.put(1, "aaa");  //1:aaa 5:e 2:bb
185         System.out.println(lru.toString());
186     }
187     
188     
189     
190 }

 

  线程安全与线程不安全


  

  线程安全就是多线程访问时,采用了加锁机制,当一个线程访问该类的某个数据时,进行保护,其他线程不能进行访问直到该线程读取完,其他线程才可使用。不会出现数据不一致或者数据污染。
  线程不安全就是不提供数据访问保护,有可能出现多个线程先后更改数据造成所得到的数据是脏数据。  

  1 package com.zuo.lru;
  2 
  3 import java.util.Iterator;
  4 import java.util.LinkedHashMap;
  5 import java.util.Map;
  6 import java.util.Map.Entry;
  7 
  8 /**
  9  * 线程安全
 10  * @author zuo
 11  *
 12  */
 13 public class LRUCacheSafe <K,V>{
 14     
 15     private final LinkedHashMap<K,V> map;
 16     
 17     private int currentCacheSize;    //当前cache的大小
 18     private int CacheCapcity; //cache最大大小
 19     private int putCount;       //put的次数
 20     private int createCount;    //create的次数
 21     private int evictionCount;  //回收的次数
 22     private int hitCount;       //命中的次数
 23     private int missCount;      //未命中次数
 24     
 25     public LRUCacheSafe(int CacheCapcity){
 26         if(CacheCapcity<=0){
 27             throw new IllegalArgumentException("CacheCapcity <= 0");
 28         }
 29         this.CacheCapcity=CacheCapcity;
 30         //将LinkedHashMap的accessOrder设置为true来实现LRU
 31         this.map=new LinkedHashMap<K,V>(0,0.75f,true);//true 就是基于访问的顺序,get一个元素后,这个元素被加到最后(使用了LRU 最近最少被使用的调度算法)
 32     }
 33     
 34     public final V get(K key){
 35         if(key==null){
 36             throw new NullPointerException("key == null");
 37         }
 38         V mapValue;
 39         synchronized (this) {
 40             mapValue=map.get(key);
 41             if(mapValue!=null){
 42                 //mapValue 不为空表示命中,hitCount+1 并返回mapValue对象
 43                 hitCount++;
 44                 return mapValue;
 45             }
 46             missCount++;
 47         }
 48         //如果未命中,则试图创建一个对象,这里create方法放回null,并没有实现创建对象的方法
 49         //如果需要事项创建对象的方法可以重写create方法。因为图片缓存时内存缓存没有命中会去文件缓存或者从网络下载,所以不需要创建。
 50         V createValue=create(key);
 51         if(createValue==null){
 52             return null;
 53         }
 54         //假如创建了新的对象,则继续往下运行
 55         synchronized (this) {
 56             createCount++;
 57             //将createValue加入到map中,并且将原来的key的对象保存到mapValue
 58             mapValue=map.put(key, createValue);
 59             if(mapValue!=null){
 60                 //如果mapValue不为空,则撤销上一步的put操作
 61                 map.put(key, mapValue);
 62             }else{
 63                 //加入新创建的对象之后需要重新计算currentCacheSize大小
 64                 currentCacheSize+=safecurrentCacheSizeOf(key, createValue);
 65             }
 66         }
 67         if(mapValue!=null){
 68             entryRemoved(false, key, createValue, mapValue);
 69             return mapValue;
 70         }else{
 71             //每次新加入对象都需要调用trimTocurrentCacheSize方法看是否回收
 72             trimTocurrentCacheSize(CacheCapcity);
 73             return createValue;
 74         }
 75     }
 76     
 77     /**
 78      * 此方法根据CacheCapcity来调整cache的大小,如果CacheCapcity传入-1,则清空缓存中的的大小
 79      * @param CacheCapcity
 80      */
 81     private void trimTocurrentCacheSize(int CacheCapcity){
 82         while(true){
 83             K key;
 84             V value;
 85             synchronized (this) {
 86                 if(currentCacheSize<0||(map.isEmpty() && currentCacheSize!=0)){
 87                     throw new IllegalStateException(getClass().getName()
 88                          + ".currentCacheSizeOf() is reporting inconsistent results!");
 89                 }
 90                 //如果当前currentCacheSize小于CacheCapcity或者map没有任何对象,则循环结束
 91                 if(currentCacheSize<=CacheCapcity || map.isEmpty()){
 92                     break;
 93                 }
 94                 //移除链表头部的元素,并进入下一次循环
 95                 Map.Entry<K, V> toEvict =map.entrySet().iterator().next();
 96                 key=toEvict.getKey();
 97                 value=toEvict.getValue();
 98                 map.remove(key);
 99                 currentCacheSize-=safecurrentCacheSizeOf(key, value);
100                 evictionCount++;//回收次数++
101             }
102             entryRemoved(true, key, value, null);
103         }
104     }
105     
106     public final V put(K key,V value){
107         if(key==null||value==null){
108             throw new NullPointerException("key == null || value == null");
109         }
110         V previous;
111         synchronized (this) {
112             putCount++;
113             currentCacheSize+=safecurrentCacheSizeOf(key, value);//currentCacheSize加上预put对象大小
114             previous=map.put(key, value);
115             if(previous!=null){
116                 //如果之前存在键为key的对象,则currentCacheSize应该减去原来对象的大小
117                 currentCacheSize-=safecurrentCacheSizeOf(key, previous);
118             }
119         }
120         if(previous!=null){
121             entryRemoved(false, key, previous, value);
122         }
123         //每次新加入的对象都需要调用trimtocurrentCacheSize方法看是否要回收
124         trimTocurrentCacheSize(CacheCapcity);
125         return previous;
126     }
127     
128     /**
129      * 从内存缓存中根据key值移除某个对象并返回该对象
130      * @param key
131      * @return
132      */
133     public final V remove(K key){
134         if(key==null){
135             throw new NullPointerException("key == null");
136         }
137         V previous;
138         synchronized (this) {
139             previous=map.remove(key);
140             if(previous!=null){
141                 currentCacheSize-=safecurrentCacheSizeOf(key, previous);
142             }
143         }
144         if(previous!=null){
145             entryRemoved(false, key, previous, null);
146         }
147         return previous;
148     }
149     
150     /**
151      * 在高速缓存未命中之后调用以计算对应键的值
152      * @param key
153      * @return 如果没有计算值,则返回计算值或NULL
154      */
155     protected V create(K key) {
156         return null;
157     }
158     
159     private int safecurrentCacheSizeOf(K key,V value){
160         int result=currentCacheSizeOf(key, value);
161         if(result<0){
162             throw new IllegalStateException("Negative currentCacheSize: " + key + "=" + value);
163         }
164         return result;
165     }
166     
167     /**
168      * 用来计算单个对象的大小,这里默认返回1
169      * @param key
170      * @param value
171      * @return
172      */
173     protected int currentCacheSizeOf(K key,V value) {
174         return 1;
175     }
176     
177     protected void entryRemoved(boolean evicted,K key,V oldValue,V newValue) {}
178     
179     /**
180      * 清空内存缓存
181      */
182     public final void evictAll(){
183         trimTocurrentCacheSize(-1);
184     }
185     
186     /**
187      * 当前cache大小
188      * @return
189      */
190     public synchronized final int currentCacheSize(){
191         return currentCacheSize;
192     }
193     /**
194      * 命中次数
195      * @return
196      */
197     public synchronized final int hitCount(){
198         return hitCount;
199     }
200     /**
201      * 未命中次数
202      * @return
203      */
204     public synchronized final int missCount(){
205         return missCount;
206     }
207     /**
208      * create次数
209      * @return
210      */
211     public synchronized final int createCount(){
212         return createCount;
213     }
214     /**
215      * put次数
216      * @return
217      */
218     public synchronized final int putCount(){
219         return putCount;
220     }
221     /**
222      * 回收次数
223      * @return
224      */
225     public synchronized final int evictionCount(){
226         return evictionCount;
227     }
228     /**
229      * 返回一个当前缓存内容的副本
230      * @return
231      */
232     public synchronized final Map<K, V> snapshot(){
233         return new LinkedHashMap<K,V>(map);
234     }
235     
236     @Override
237     public synchronized final String toString() {
238         int accesses =hitCount+missCount;
239         int hitPercent=accesses!=0?(100 * hitCount/accesses):0;//缓存命中率是判断加速效果好坏的重要因素
240         Iterator<Entry<K, V>> iterator= map.entrySet().iterator();  
241         while(iterator.hasNext())  
242         {  
243             Entry<K, V> entry = iterator.next();  
244             System.out.println(entry.getKey()+":"+entry.getValue());  
245         } 
246         return String.format("LruCache[缓存最大大小=%d,命中次数=%d,未命中次数=%d,命中率=%d%%]",
247                         CacheCapcity, hitCount, missCount, hitPercent);
248     }
249     
250     public static void main(String[] args) {
251 
252         LRUCacheSafe<Integer,String> lru = new LRUCacheSafe<Integer,String>(3);
253         System.out.println("--------------------开始使用LRU缓存---------------");
254        
255         lru.put(1, "7");    
256         System.out.println(lru.toString());
257         lru.put(2, "0");    
258         System.out.println(lru.toString());
259         lru.put(3, "1");    
260         System.out.println(lru.toString());
261         lru.put(4, "2");     
262         System.out.println(lru.toString());
263         lru.put(1, "0");   
264         System.out.println(lru.toString());
265         lru.put(2, "3");   
266         System.out.println(lru.toString());
267         lru.put(5, "0");   
268         System.out.println(lru.toString());
269         lru.put(6, "4");   
270         System.out.println(lru.toString());
271         lru.put(7, "2");   
272         System.out.println(lru.toString());
273         lru.put(8, "3");   
274         System.out.println(lru.toString());
275         lru.put(9, "0");   
276         System.out.println(lru.toString());
277         lru.put(10, "3");   
278         System.out.println(lru.toString());
279         lru.put(11, "2");   
280         System.out.println(lru.toString());
281         lru.put(12, "1");   
282         System.out.println(lru.toString());
283         lru.put(13, "2");   
284         System.out.println(lru.toString());
285         lru.put(14, "0");   
286         System.out.println(lru.toString());
287         lru.put(15, "1");   
288         System.out.println(lru.toString());
289         lru.put(16, "7");   
290         System.out.println(lru.toString());
291         lru.put(17, "0");   
292         System.out.println(lru.toString());
293         lru.put(18, "1");   
294         System.out.println(lru.toString());
295         lru.get(1);         
296         lru.get(18);         
297         lru.get(2);         
298         System.out.println(lru.toString());
299         lru.remove(16);     
300         System.out.println(lru.toString());
301     }
302 
303 }

 

 

 

posted @ 2018-05-30 16:43  ZzUuOo666  阅读(1177)  评论(0编辑  收藏  举报