LinkedHashMap內部維護了一個雙向鏈表,能保證元素按插入的順序訪問,也能以訪問順序訪問。java
底層經過LinkedList+HashMap實現node
/** * 雙向鏈表頭節點 */ transient LinkedHashMap.Entry<K,V> head; /** * 雙向鏈表尾節點 */ transient LinkedHashMap.Entry<K,V> tail; /** * 是否按訪問順序排序,默認false,只是按照插入的順序排序,true,會根據訪問的順序排序(訪問確定也包括插入啊) */ final boolean accessOrder;
void afterNodeAccess(Node<K,V> e) { // move node to last LinkedHashMap.Entry<K,V> last; // 若是accessOrder爲true,而且訪問的節點不是尾節點 if (accessOrder && (last = tail) != e) { LinkedHashMap.Entry<K,V> p = (LinkedHashMap.Entry<K,V>)e, b = p.before, a = p.after; // 把p節點從雙向鏈表中移除 p.after = null; if (b == null) head = a; else b.after = a; if (a != null) a.before = b; else last = b; // 把p節點放到雙向鏈表的末尾 if (last == null) head = p; else { p.before = last; last.after = p; } // 尾節點等於p tail = p; ++modCount; } }
若是一個數據在最近一段時間沒有被訪問到,那麼在未來它被訪問的可能性也很小。也就是說,當限定的空間已存滿數據時,應當把最近最少使用的數據淘汰。算法
使用LinkedHashMap實現
LinkedHashMap底層就是用的HashMap加雙鏈表實現的,並且自己已經實現了按照訪問順序的存儲。此外,LinkedHashMap中自己就實現了一個方法主要是利用他的這個方法,removeEldestEntry用於判斷是否須要移除最不常讀取的數,方法默認是直接返回false,不會移除元素,因此須要重寫該方法。即當緩存滿後就移除最不經常使用的數。數組
public class LRUCache<K, V> extends LinkedHashMap<K, V> { private static final int MAX_CACHE_SIZE = 100; private int limit; public LRUCache() { this(MAX_CACHE_SIZE); } public LRUCache(int cacheSize) { super(cacheSize, 0.75f, true);//這裏是true喲,因此訪問的話也會被更新到末尾 this.limit = cacheSize; } public V save(K key, V val) { return put(key, val); } public V getOne(K key) { return get(key); } public boolean exists(K key) { return containsKey(key); } /** * 判斷節點數是否超限 * @param eldest * @return 超限返回 true,不然返回 false */ @Override protected boolean removeEldestEntry(Map.Entry eldest) { return size() > limit; } @Override public String toString() { StringBuilder sb = new StringBuilder(); for (Map.Entry<K, V> entry : entrySet()) { sb.append(String.format("%s:%s ", entry.getKey(), entry.getValue())); } return sb.toString(); } public static void main(String[] args){ LRUCache<String, Integer> cache = new LRUCache<>(3); for (int i = 0; i < 10; i++) { cache.save("I" + i, i * i); } System.out.println("插入10個鍵值對後,緩存內容爲:"); System.out.println(cache + "\n"); System.out.println("訪問鍵值爲I8的節點後,緩存內容爲:"); cache.getOne("I8"); System.out.println(cache + "\n"); System.out.println("插入鍵值爲I1的鍵值對後,緩存內容:"); cache.save("I1", 1); System.out.println(cache); } }
輸出:
插入10個鍵值對後,緩存內容爲: I7:49 I8:64 I9:81 訪問鍵值爲I8的節點後,緩存內容爲: I7:49 I9:81 I8:64 插入鍵值爲I1的鍵值對後,緩存內容: I9:81 I8:64 I1:1
參考:緩存
https://www.imooc.com/article/67024app
http://cmsblogs.com/?p=4733ide
1.LRU-Kui
LRU-K中的K表明最近使用的次數,所以LRU能夠認爲是LRU-1。LRU-K的主要目的是爲了解決LRU算法「緩存污染」的問題,其核心思想是將「最近使用過1次」的判斷標準擴展爲「最近使用過K次」。
相比LRU,LRU-K須要多維護一個隊列,用於記錄全部緩存數據被訪問的歷史。只有當數據的訪問次數達到K次的時候,纔將數據放入緩存。當須要淘汰數據時,LRU-K會淘汰第K次訪問時間距當前時間最大的數據。數據第一次被訪問時,加入到歷史訪問列表,若是在訪問歷史列表中沒有達到K次訪問,則按照必定的規則(FIFO,LRU)淘汰;當訪問歷史隊列中的數據訪問次數達到K次後,將數據索引從歷史隊列中刪除,將數據移到緩存隊列中,並緩存數據,緩存隊列從新按照時間排序;緩存數據隊列中被再次訪問後,從新排序,須要淘汰數據時,淘汰緩存隊列中排在末尾的數據,即「淘汰倒數K次訪問離如今最久的數據」。LRU-K具備LRU的優勢,同時還能避免LRU的缺點,實際應用中LRU-2是綜合最優的選擇。因爲LRU-K還須要記錄那些被訪問過、但尚未放入緩存的對象,所以內存消耗會比LRU要多。this
2.two queue
Two queues(如下使用2Q代替)算法相似於LRU-2,不一樣點在於2Q將LRU-2算法中的訪問歷史隊列(注意這不是緩存數據的)改成一個FIFO緩存隊列,即:2Q算法有兩個緩存隊列,一個是FIFO隊列,一個是LRU隊列。當數據第一次訪問時,2Q算法將數據緩存在FIFO隊列裏面,當數據第二次被訪問時,則將數據從FIFO隊列移到LRU隊列裏面,兩個隊列各自按照本身的方法淘汰數據。新訪問的數據插入到FIFO隊列中,若是數據在FIFO隊列中一直沒有被再次訪問,則最終按照FIFO規則淘汰;若是數據在FIFO隊列中再次被訪問到,則將數據移到LRU隊列頭部,若是數據在LRU隊列中再次被訪問,則將數據移動LRU隊列頭部,LRU隊列淘汰末尾的數據。spa
3.Multi Queue(MQ)
MQ算法根據訪問頻率將數據劃分爲多個隊列,不一樣的隊列具備不一樣的訪問優先級,其核心思想是:優先緩存訪問次數多的數據。詳細的算法結構圖以下,Q0,Q1....Qk表明不一樣的優先級隊列,Q-history表明從緩存中淘汰數據,但記錄了數據的索引和引用次數的隊列:新插入的數據放入Q0,每一個隊列按照LRU進行管理,當數據的訪問次數達到必定次數,須要提高優先級時,將數據從當前隊列中刪除,加入到高一級隊列的頭部;爲了防止高優先級數據永遠不會被淘汰,當數據在指定的時間裏沒有被訪問時,須要下降優先級,將數據從當前隊列刪除,加入到低一級的隊列頭部;須要淘汰數據時,從最低一級隊列開始按照LRU淘汰,每一個隊列淘汰數據時,將數據從緩存中刪除,將數據索引加入Q-history頭部。若是數據在Q-history中被從新訪問,則從新計算其優先級,移到目標隊列頭部。Q-history按照LRU淘汰數據的索引。
參考:
https://blog.csdn.net/elricboa/article/details/78847305
除了能夠用java自帶的集合類LInkedHashMap來實現,咱們也能夠本身寫一個,但基本原理和LinkedHashMap同樣,底層採用Hashmap,加上一個隊列:
基本需求:
public class LRUAbstractMap extends java.util.AbstractMap { /** * 檢查是否超期線程 */ private ExecutorService checkTimePool ; /** * map 最大size */ private final static int MAX_SIZE = 1024 ; //用一個阻塞隊列存儲 private final static ArrayBlockingQueue<Node> QUEUE = new ArrayBlockingQueue<>(MAX_SIZE) ; private int arraySize ; /** * 數組 */ private Object[] arrays ;//這個數組有點像hashmap /** * 超時時間 */ private final static Long EXPIRE_TIME = 60 * 60 * 1000L ; /** * 整個 Map 的大小 */ private volatile AtomicInteger size ; public LRUAbstractMap() { arraySize = 1024; arrays = new Object[arraySize] ; //開啓一個線程檢查最早放入隊列的值是否超期 executeCheckTime(); } /** * 開啓一個線程檢查最早放入隊列的值是否超期 設置爲守護線程 */ private void executeCheckTime() { ThreadFactory namedThreadFactory = new ThreadFactoryBuilder().setNameFormat("check-thread-%d") .setDaemon(true) .build(); checkTimePool = new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new ArrayBlockingQueue<>(1),namedThreadFactory,new ThreadPoolExecutor.AbortPolicy()); checkTimePool.execute(new CheckTimeThread()) ; } @Override public Object put(Object key, Object value) { int hash = hash(key); int index = hash % arraySize ; Node currentNode = (Node) arrays[index] ; if (currentNode == null){ arrays[index] = new Node(null,null, key, value); //寫入隊列 QUEUE.offer((Node) arrays[index]) ; }else { Node cNode = currentNode ; Node nNode = cNode ; //存在就覆蓋 if (nNode.key == key){ cNode.val = value ; } while (nNode.next != null){ //key 存在 就覆蓋 簡單判斷 if (nNode.key == key){ nNode.val = value ;//去更新隊列和Hashmap裏面的值 break ; }else { //不存在就新增鏈表 Node node = new Node(nNode,null,key,value) ; //寫入隊列 QUEUE.offer(currentNode) ; cNode.next = node ; } nNode = nNode.next ; } } return null ; } @Override public Object get(Object key) { int hash = hash(key) ; int index = hash % arraySize ; Node currentNode = (Node) arrays[index] ; if (currentNode == null){ return null ; } if (currentNode.next == null){ //更新時間 currentNode.setUpdateTime(System.currentTimeMillis()); //沒有衝突 return currentNode ; } Node nNode = currentNode ; while (nNode.next != null){ if (nNode.key == key){ //更新時間 currentNode.setUpdateTime(System.currentTimeMillis()); return nNode ; } nNode = nNode.next ; } return super.get(key); } @Override public Object remove(Object key) { int hash = hash(key) ; int index = hash % arraySize ; Node currentNode = (Node) arrays[index] ; if (currentNode == null){ return null ; } if (currentNode.key == key){ arrays[index] = null ; //移除隊列 QUEUE.poll(); return currentNode ; } Node nNode = currentNode ; while (nNode.next != null){ if (nNode.key == key){ //在鏈表中找到了 把上一個節點的 next 指向當前節點的下一個節點 nNode.pre.next = nNode.next ; nNode = null ; //移除隊列 QUEUE.poll(); return nNode; } nNode = nNode.next ; } return super.remove(key); } /** * 增長size /** * 鏈表 */ private class Node{ private Node next ; private Node pre ; private Object key ; private Object val ; private Long updateTime ; public Node(Node pre,Node next, Object key, Object val) { this.pre = pre ; this.next = next; this.key = key; this.val = val; this.updateTime = System.currentTimeMillis() ; } public void setUpdateTime(Long updateTime) { this.updateTime = updateTime; } public Long getUpdateTime() { return updateTime; } } /** * copy HashMap 的 hash 實現 * @param key * @return */ public int hash(Object key) { int h; return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16); } /** * 判斷是否中止 flag */ private volatile boolean flag = true ; private class CheckTimeThread implements Runnable{ @Override public void run() { while (flag){ try { Node node = QUEUE.poll(); if (node == null){ continue ; } Long updateTime = node.getUpdateTime() ; if ((updateTime - System.currentTimeMillis()) >= EXPIRE_TIME){ remove(node.key) ; } } catch (Exception e) { } } } } }