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 }

 

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM