GuavaCache學習筆記一:自定義LRU算法的緩存實現
前言
今天在看GuavaCache緩存相關的源碼,這裏想到先本身手動實現一個LRU算法。因而乎便想到LinkedHashMap和LinkedList+HashMap, 這裏僅僅是做爲簡單的複習一下。html
LRU
LRU(Least recently used,最近最少使用)算法根據數據的歷史訪問記錄來進行淘汰數據,其核心思想是「若是數據最近被訪問過,那麼未來被訪問的概率也更高」。java
代碼實現原理
LinkedList + HashMap: LinkedList實際上是一個雙向鏈表,咱們能夠經過get和put來設置最近請求key的位置,而後hashMap去存儲數據
LinkedHashMap:LinkedHashMap是繼承自HashMap,只不過Map中的Node節點改成了雙向節點,雙向節點能夠維護添加的順序,在LinkedHashMap的構造函數中有一個accessOrder, 當設置爲true後,put和get會自動維護最近請求的位置到last。算法
LinkedList+HashMap代碼實現
LRUCache接口:數組
/** * @Description: * @Author: wangmeng * @Date: 2018/12/8-10:49 */ public class LinkedListLRUTest { public static void main(String[] args) { LRUCache<String, String> cache = new LinkedListLRUCache<>(3); cache.put("1", "1"); cache.put("2", "2"); cache.put("3", "3"); System.out.println(cache); cache.put("4", "4"); System.out.println(cache); System.out.println(cache.get("2")); System.out.println(cache); } }
LinkedList實現:緩存
/** * @Description:使用LinkedList+HashMap來實現LRU算法 * @Author: wangmeng * @Date: 2018/12/8-10:41 */ public class LinkedListLRUCache<K, V> implements LRUCache<K, V> { private final int limit; private final LinkedList<K> keys = new LinkedList<>(); private final Map<K, V> cache = Maps.newHashMap(); public LinkedListLRUCache(int limit) { this.limit = limit; } @Override public void put(K key, V value) { Preconditions.checkNotNull(key); Preconditions.checkNotNull(value); if (keys.size() >= limit) { K oldesKey = keys.removeFirst(); cache.remove(oldesKey); } keys.addLast(key); cache.put(key, value); } @Override public V get(K key) { boolean exist = keys.remove(key); if (!exist) { return null; } keys.addLast(key); return cache.get(key); } @Override public void remove(K key) { boolean exist = keys.remove(key); if (exist) { keys.remove(key); cache.remove(key); } } @Override public int size() { return keys.size(); } @Override public void clear() { keys.clear(); cache.clear(); } @Override public int limit() { return this.limit; } @Override public String toString() { StringBuilder builder = new StringBuilder(); for (K key : keys) { builder.append(key).append("=").append(cache.get(key)).append(";"); } return builder.toString(); } }
LinkedList測試類:安全
/** * @Description: * @Author: wangmeng * @Date: 2018/12/8-10:49 */ public class LinkedListLRUTest { public static void main(String[] args) { LRUCache<String, String> cache = new LinkedListLRUCache<>(3); cache.put("1", "1"); cache.put("2", "2"); cache.put("3", "3"); System.out.println(cache); cache.put("4", "4"); System.out.println(cache); System.out.println(cache.get("2")); System.out.println(cache); } }
LinkedList測試類返回值:app
1=1;2=2;3=3; 2=2;3=3;4=4; 2 3=3;4=4;2=2;
LinkedHashMap實現
/** * @Description: 不是一個線程安全的類,這裏是使用LinkedHashMap來作LRU算法 * @Author: wangmeng * @Date: 2018/12/8-10:14 */ public class LinkedHashLRUCache<K, V> implements LRUCache<K, V> { private static class InternalLRUCache<K, V> extends LinkedHashMap<K, V> { final private int limit; private InternalLRUCache(int limit) { super(16, 0.75f, true); this.limit = limit ; } //實現remove元素的方法,這個是重寫了LinkedHashMap中的方法。由於在HashMap的putVal會調用afterNodeInsertion(), 而這個方法會判斷removeEldestEntry方法。 @Override protected boolean removeEldestEntry(Map.Entry<K, V> eldest) { return size() > limit; } } private final int limit; //使用組合關係優於繼承,這裏只對外暴漏LRUCache中的方法 private final InternalLRUCache<K, V> internalLRUCache; public LinkedHashLRUCache(int limit) { Preconditions.checkArgument(limit > 0, "The limit big than zero."); this.limit = limit; this.internalLRUCache = new InternalLRUCache(limit); } @Override public void put(K key, V value) { this.internalLRUCache.put(key, value); } @Override public V get(K key) { return this.internalLRUCache.get(key); } @Override public void remove(K key) { this.internalLRUCache.remove(key); } @Override public int size() { return this.internalLRUCache.size(); } @Override public void clear() { this.internalLRUCache.clear(); } @Override public int limit() { return this.limit; } @Override public String toString() { return internalLRUCache.toString(); } }
LinkedHashMap測試類:ide
/** * @Description: * @Author: wangmeng * @Date: 2018/12/8-10:30 */ public class LinkedHashLRUTest { public static void main(String[] args) { LRUCache<String, String> cache = new LinkedHashLRUCache<>(3); cache.put("1", "1"); cache.put("2", "2"); cache.put("3", "3"); System.out.println(cache); cache.put("4", "4"); System.out.println(cache); System.out.println(cache.get("2")); System.out.println(cache); } }
LinkedHashMap測試結果:函數
{1=1, 2=2, 3=3} {2=2, 3=3, 4=4} 2 {3=3, 4=4, 2=2}
文章目錄
簡介
實現LRU
LinkedHashMap中LRU算法實現
簡介
LRU全稱是Least Recently Used,即最近最久未使用的意思。post
LRU算法的設計原則是:若是一個數據在最近一段時間沒有被訪問到,那麼在未來它被訪問的可能性也很小。也就是說,當限定的空間已存滿數據時,應當把最久沒有被訪問到的數據淘汰。
實現LRU
1.用一個數組來存儲數據,給每個數據項標記一個訪問時間戳,每次插入新數據項的時候,先把數組中存在的數據項的時間戳自增,並將新數據項的時間戳置爲0並插入到數組中。每次訪問數組中的數據項的時候,將被訪問的數據項的時間戳置爲0。當數組空間已滿時,將時間戳最大的數據項淘汰。
2.利用一個鏈表來實現,每次新插入數據的時候將新數據插到鏈表的頭部;每次緩存命中(即數據被訪問),則將數據移到鏈表頭部;那麼當鏈表滿的時候,就將鏈表尾部的數據丟棄。
3.利用鏈表和hashmap。當須要插入新的數據項的時候,若是新數據項在鏈表中存在(通常稱爲命中),則把該節點移到鏈表頭部,若是不存在,則新建一個節點,放到鏈表頭部,若緩存滿了,則把鏈表最後一個節點刪除便可。在訪問數據的時候,若是數據項在鏈表中存在,則把該節點移到鏈表頭部,不然返回-1。這樣一來在鏈表尾部的節點就是最近最久未訪問的數據項。
對於第一種方法,須要不停地維護數據項的訪問時間戳,另外,在插入數據、刪除數據以及訪問數據時,時間複雜度都是O(n)。對於第二種方法,鏈表在定位數據的時候時間複雜度爲O(n)。因此在通常使用第三種方式來是實現LRU算法。
LinkedHashMap中LRU算法實現
/**
* @Author: Kingcym
* @Description: 非線程安全
* @Date: 2018/11/11 19:09
*/
public class LinkedHashLRUcache<k, v> {
/**
* LinkedHashMap(自身實現了LRU算法)
* 1.有序
* 2.每次訪問一個元素,都會提到最後面去
*/
private static class InternalLRUcache<k, v> extends LinkedHashMap<k, v> {
private final int limit;
private InternalLRUcache(int limit) {
super(16, 0.75f, true);
this.limit = limit;
}
//是否刪除最老的數據
@Override
protected boolean removeEldestEntry(Map.Entry<k, v> eldest) {
return size() > limit;
}
}
private final int limit;
private final InternalLRUcache<k, v> internalLRUcache;
public LinkedHashLRUcache(int limit) {
Assert.state(limit > 0, "limit必須大於0");
this.limit = limit;
this.internalLRUcache = new InternalLRUcache(limit);
}
public void put(k key, v value) {
this.internalLRUcache.put(key, value);
}
public v get(k key) {
return this.internalLRUcache.get(key);
}
public void remove(k key) {
this.internalLRUcache.remove(key);
}
public int size() {
return this.internalLRUcache.size();
}
public void clear() {
this.internalLRUcache.clear();
}
public String toString() {
return internalLRUcache.toString();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
當存在熱點數據時,LRU的效率很好,但偶發性的、週期性的批量操做會致使LRU命中率急劇降低,緩存污染狀況比較嚴重。
參考: GuavaCache學習筆記一:自定義LRU算法的緩存實現