1. 程式人生 > >精進之路之lru

精進之路之lru

原理
LRU(Least recently used,最近最少使用)演算法根據資料的歷史訪問記錄來進行淘汰資料,其核心思想是“如果資料最近被訪問過,那麼將來被訪問的機率也更高”。

實現1
最常見的實現是使用一個連結串列儲存快取資料,詳細演算法實現如下:

1. 新資料插入到連結串列頭部;
2. 每當快取命中(即快取資料被訪問),則將資料移到連結串列頭部;
3. 當連結串列滿的時候,將連結串列尾部的資料丟棄。
分析
【命中率】
當存在熱點資料時,LRU的效率很好,但偶發性的、週期性的批量操作會導致LRU命中率急劇下降,快取汙染情況比較嚴重。
【複雜度】
實現簡單。
【代價】
命中時需要遍歷連結串列,找到命中的資料塊索引,然後需要將資料移到頭部。

 

使用LinkedHashMap實現
     LinkedHashMap底層就是用的HashMap加雙鏈表實現的,而且本身已經實現了按照訪問順序的儲存。此外,LinkedHashMap中本身就實現了一個方法removeEldestEntry用於判斷是否需要移除最不常讀取的數,方法預設是直接返回false,不會移除元素,所以需要重寫該方法。即當快取滿後就移除最不常用的數。

 1 public class LRUCache<K, V> extends LinkedHashMap<K, V> {
 2 
 3     private
static final long serialVersionUID = 1L; 4 5 //快取大小 6 private int cacheSize; 7 8 public LRUCache(int cacheSize) { 9 //第三個引數true是關鍵 10 super(10, 0.75f, true); 11 this.cacheSize = cacheSize; 12 } 13 14 /** 15 * 快取是否已滿 16 */ 17 @Override 18 protected
boolean removeEldestEntry(Map.Entry<K, V> eldest) { 19 boolean r = size() > cacheSize; 20 if (r) { 21 System.out.println("清除快取key:" + eldest.getKey()); 22 } 23 return r; 24 } 25 26 //測試 27 public static void main(String[] args) { 28 LRUCache<String, String> cache = new LRUCache<String, String>(5); 29 cache.put("1", "1"); 30 cache.put("2", "2"); 31 cache.put("3", "3"); 32 cache.put("4", "4"); 33 cache.put("5", "5"); 34 35 System.out.println("初始化:"); 36 System.out.println(cache.keySet()); 37 System.out.println("訪問3:"); 38 cache.get("3"); 39 System.out.println(cache.keySet()); 40 System.out.println("訪問2:"); 41 cache.get("2"); 42 System.out.println(cache.keySet()); 43 System.out.println("增加資料6,7:"); 44 cache.put("6", "6"); 45 cache.put("7", "7"); 46 System.out.println(cache.keySet()); 47 }

實現2

LRUCache的連結串列+HashMap實現

傳統意義的LRU演算法是為每一個Cache物件設定一個計數器,每次Cache命中則給計數器+1,而Cache用完,需要淘汰舊內容,放置新內容時,就檢視所有的計數器,並將最少使用的內容替換掉。

它的弊端很明顯,如果Cache的數量少,問題不會很大, 但是如果Cache的空間過大,達到10W或者100W以上,一旦需要淘汰,則需要遍歷所有計算器,其效能與資源消耗是巨大的。效率也就非常的慢了。
它的原理: 將Cache的所有位置都用雙連表連線起來,當一個位置被命中之後,就將通過調整連結串列的指向,將該位置調整到連結串列頭的位置,新加入的Cache直接加到連結串列頭中。
這樣,在多次進行Cache操作後,最近被命中的,就會被向連結串列頭方向移動,而沒有命中的,而想連結串列後面移動,連結串列尾則表示最近最少使用的Cache。
當需要替換內容時候,連結串列的最後位置就是最少被命中的位置,我們只需要淘汰連結串列最後的部分即可。
上面說了這麼多的理論, 下面用程式碼來實現一個LRU策略的快取。
非執行緒安全,若實現安全,則在響應的方法加鎖。

  1 public class LRUCacheDemo<K, V> {
  2 
  3     private int currentCacheSize;
  4     private int CacheCapcity;
  5     private HashMap<K, CacheNode> caches;
  6     private CacheNode first;
  7     private CacheNode last;
  8 
  9     public LRUCacheDemo(int size) {
 10         currentCacheSize = 0;
 11         this.CacheCapcity = size;
 12         caches = new HashMap<>(size);
 13     }
 14 
 15     public void put(K k, V v) {
 16         CacheNode node = caches.get(k);
 17         if (node == null) {
 18             if (caches.size() >= CacheCapcity) {
 19                 caches.remove(last.key);
 20                 removeLast();
 21             }
 22             node = new CacheNode();
 23             node.key = k;
 24         }
 25         node.value = v;
 26         moveToFirst(node);
 27         caches.put(k, node);
 28     }
 29 
 30     public Object get(K k) {
 31         CacheNode node = caches.get(k);
 32         if (node == null) {
 33             return null;
 34         }
 35         moveToFirst(node);
 36         return node.value;
 37     }
 38 
 39     public Object remove(K k) {
 40         CacheNode node = caches.get(k);
 41         if (node != null) {
 42             if (node.pre != null) {
 43                 node.pre.next = node.next;
 44             }
 45             if (node.next != null) {
 46                 node.next.pre = node.pre;
 47             }
 48             if (node == first) {
 49                 first = node.next;
 50             }
 51             if (node == last) {
 52                 last = node.pre;
 53             }
 54         }
 55         return caches.remove(k);
 56     }
 57 
 58     public void clear() {
 59         first = null;
 60         last = null;
 61         caches.clear();
 62     }
 63 
 64     private void moveToFirst(CacheNode node) {
 65         if (first == node) {
 66             return;
 67         }
 68         if (node.next != null) {
 69             node.next.pre = node.pre;
 70         }
 71         if (node.pre != null) {
 72             node.pre.next = node.next;
 73         }
 74         if (node == last) {
 75             last = last.pre;
 76         }
 77         if (first == null || last == null) {
 78             first = last = node;
 79             return;
 80         }
 81         node.next = first;
 82         first.pre = node;
 83         first = node;
 84         first.pre = null;
 85     }
 86 
 87     private void removeLast() {
 88         if (last != null) {
 89             last = last.pre;
 90             if (last == null) {
 91                 first = null;
 92             } else {
 93                 last.next = null;
 94             }
 95         }
 96     }
 97 
 98     @Override
 99     public String toString() {
100         StringBuilder sb = new StringBuilder();
101         CacheNode node = first;
102         while (node != null) {
103             sb.append(String.format("%s:%s ", node.key, node.value));
104             node = node.next;
105         }
106         return sb.toString();
107     }
108 
109     class CacheNode {
110         CacheNode pre;
111         CacheNode next;
112         Object key;
113         Object value;
114 
115         public CacheNode() {
116         }
117     }
118 
119     public static void main(String[] args) {
120         LRUCache<Integer, String> lru = new LRUCache<Integer, String>(3);
121         lru.put(1, "a"); // 1:a
122         System.out.println(lru.toString());
123         lru.put(2, "b"); // 2:b 1:a
124         System.out.println(lru.toString());
125         lru.put(3, "c"); // 3:c 2:b 1:a 
126         System.out.println(lru.toString());
127         lru.put(4, "d"); // 4:d 3:c 2:b
128         System.out.println(lru.toString());
129         lru.put(1, "aa"); // 1:aa 4:d 3:c
130         System.out.println(lru.toString());
131         lru.put(2, "bb"); // 2:bb 1:aa 4:d
132         System.out.println(lru.toString());
133         lru.put(5, "e"); // 5:e 2:bb 1:aa
134         System.out.println(lru.toString());
135         lru.get(1); // 1:aa 5:e 2:bb
136         System.out.println(lru.toString());
137         lru.remove(11); // 1:aa 5:e 2:bb
138         System.out.println(lru.toString());
139         lru.remove(1); //5:e 2:bb
140         System.out.println(lru.toString());
141         lru.put(1, "aaa"); //1:aaa 5:e 2:bb
142         System.out.println(lru.toString());
143     }
144 }

 本文參考整理於  https://blog.csdn.net/wangxilong1991/article/details/70172302 ,感謝原作者的精彩分享!!!