設計並實現最近最久未使用(Least Recently Used)緩存。html
連接:https://oj.leetcode.com/problems/lru-cache/java
題目描述:node
Design and implement a data structure for Least Recently Used (LRU) cache. It should support the following operations: get and set.mysql
get(key) - Get the value (will always be positive) of the key if the key exists in the cache, otherwise return -1.
set(key, value) - Set or insert the value if the key is not already present. When the cache reached its capacity, it should invalidate the least recently used item before inserting a new item.面試設計並實現最近最久未使用的緩存數據結構,支持 get 和 set 操做.算法
get()-若是 key 存在,返回對應的 value 值,不然返回 -1.sql
set()-插入 key 對應的 value 到緩存中,若是緩存已滿,將最近最久未使用的元素從緩存中移除。緩存
要實現這個設計,咱們先回顧一下大學課堂上的知識。
LRU,即最近最少使用,是操做系統內存管理的一種頁面置換算法,
常見的頁面置換算法,最佳置換算法(OPT,理想置換算法),先進先出置換算法(FIFO),
最近最久未使用算法(LRU),最少使用算法。數據結構
其中,最佳置換算法是一種理想狀況下的頁面置換算法,實際上不可能實現。該算法的基本思想是發生缺頁時,有些頁面在內存中,其中有一頁將很快被訪問(也包含緊接着的下一條指令的那頁),而其餘頁面則可能要到十、100或者1000條指令後纔會被訪問,每一個頁面均可以用在該頁面首次被訪問前所要執行的指令數進行標記。最佳頁面置換算法規定標記最大的頁應該被置換。但當缺頁發生時,操做系統沒法知道各個頁面下一次是在何時被訪問。這個算法沒法實現,但能夠用於對可實現算法的性能進行衡量。app
另外兩種主要算法,LFU算法-實現緩存,FIFO算法-實現緩存,能夠查看這裏。
LRU的實現方法有不少,傳統的LRU實現方法:
1.計數器。最簡單的狀況是使每一個頁表項對應一個使用時間字段,並給CPU增長一個邏輯時鐘或計數器。每次存儲訪問,該時鐘都加1。每當訪問一個頁面時,時鐘寄存器的內容就被複制到相應頁表項的使用時間字段中。這樣咱們就能夠始終保留着每一個頁面最後訪問的「時間」。在置換頁面時,選擇該時間值最小的頁面。
2.棧。用一個棧保留頁號。每當訪問一個頁面時,就把它從棧中取出放在棧頂上。這樣一來,棧頂老是放有目前使用最多的頁,而棧底放着目前最少使用的頁。因爲要從棧的中間移走一項,因此要用具備頭尾指針的雙向鏈連起來。
Java語言能夠利用 LinkedHashMap, LinkedHashMap 是有序的哈希表,能夠保存記錄的插入順序,而且按使用順序排列。
重寫其中的removeEldestEntry(Map.Entry)方法,就能夠實現LRU算法。
我看了一下,在Mysql Jdbc Util和Apache的不少Jar包中,都是使用LinkedHashMap實現LRUCache。
下面的代碼來自mysql-connector-java-5.1.18-bin.jar。
package com.mysql.jdbc.util; import java.util.LinkedHashMap; import java.util.Map; public class LRUCache extends LinkedHashMap { public LRUCache(int maxSize) { super(maxSize, 0.75F, true); maxElements = maxSize; } protected boolean removeEldestEntry(java.util.Map.Entry eldest) { return size() > maxElements; } private static final long serialVersionUID = 1L; protected int maxElements; }
不過LeetCode的OJ不支持這樣實現,將上面的代碼修改後提交,提示 Comoile Error .
JDK中,LinkedHashMap是經過繼承HashMap,維護一個雙鏈表實現,
當某個Cache位置被命中,經過調整鏈表的指向將該位置調整到頭位置,新加入的內容直接放在鏈表頭,在屢次進行Cache操做後,最近使用的Cache就會向鏈表頭部移動,鏈表尾部就是命中次數最少,最久未使用的Cache。空間充滿時,移除尾部的數據就能夠了。
題目有幾點須要注意,一個是Key不存在的狀況,一個是緩存設計要求Key惟一。
下面使用雙向鏈表(雙鏈表)實現LRU Cache,如下代碼提交AC。
import java.util.HashMap; /** * Design and implement a data structure for Least Recently Used (LRU) cache. * It should support the following operations: get and set. * get(key) - Get the value (will always be positive) of the key if the key exists in the cache, otherwise return -1. * set(key, value) - Set or insert the value if the key is not already present. When the cache reached its capacity, * it should invalidate the least recently used item before inserting a new item. * 近期最少使用算法 設計緩存 */ public class LRUCache { private int cacheSize;//緩存容量 private int currentSize;//當前容量 private HashMap<Object, CacheNode> nodes;//緩存容器 private CacheNode head;//鏈表頭 private CacheNode last;//鏈表尾 class CacheNode{ CacheNode prev;//前一節點 CacheNode next;//後一節點 int value;//值 int key;//鍵 CacheNode() { } } //初始化緩存 public LRUCache(int capacity) { currentSize=0; cacheSize=capacity; nodes=new HashMap<Object, CacheNode>(capacity); } public Integer get(int key) { CacheNode node = nodes.get(key); if (node != null) { move(node); return node.value; } else { return -1;//error code } } public void set(int key, int value) { CacheNode node = nodes.get(key); //重複Key if(node!=null){ node.value=value; move(node); nodes.put(key, node); }else {//key未重複,正常流程 node =new CacheNode(); if(currentSize>=cacheSize){ if (last != null){//緩存已滿,進行淘汰 nodes.remove(last.key);} removeLast();//移除鏈表尾部並後移 }else{ currentSize++; } node.key=key; node.value=value; move(node); nodes.put(key, node); } } //移動鏈表節點至頭部 private void move(CacheNode cacheNode){ if(cacheNode==head) return; //連接先後節點 if(cacheNode.prev!=null) cacheNode.prev.next=cacheNode.next; if(cacheNode.next!=null) cacheNode.next.prev=cacheNode.prev; //頭尾節點 if (last == cacheNode) last = cacheNode.prev; if (head != null) { cacheNode.next = head; head.prev = cacheNode; } //移動後的鏈表 head = cacheNode; cacheNode.prev = null; //節點惟一的狀況 if (last == null) last = head; } //移除指定緩存 public void remove(int key){ CacheNode cacheNode = nodes.get(key); if (cacheNode != null) { if (cacheNode.prev != null) { cacheNode.prev.next = cacheNode.next; } if (cacheNode.next != null) { cacheNode.next.prev = cacheNode.prev; } if (last == cacheNode) last = cacheNode.prev; if (head == cacheNode) head = cacheNode.next; } } //刪除尾部的結點,即去除最近最久未使用數據 private void removeLast(){ if(last!=null){ if(last.prev!=null){ last.prev.next=null; }else{//空間大小爲1的狀況 head = null; } last = last.prev; } } public void clear() { head = null; last = null; } //測試用例 // public static void main(String[] args){ // LRUCache lCache=new LRUCache(2); // lCache.set(2, 1); // lCache.set(1, 1); // lCache.set(2, 3); // lCache.set(4, 1); // System.out.println(lCache.get(1)); // System.out.println(lCache.get(2)); // // } }
下面是提交中遇到的幾組測試用例:
Input: 2,[get(2),set(2,6),get(1),set(1,5),set(1,2),get(1),get(2)] Expected: [-1,-1,2,6]
Input: 2,[set(2,1),set(1,1),set(2,3),set(4,1),get(1),get(2)] Expected: [-1,3]
Input: 1,[get(0)] Expected: [-1]
同步:http://www.cnblogs.com/binyue/
引子:
咱們平時總會有一個電話本記錄全部朋友的電話,可是,若是有朋友常常聯繫,那些朋友的電話號碼不用翻電話本咱們也能記住,可是,若是長時間沒有聯繫了,要再次聯繫那位朋友的時候,咱們又不得不求助電話本,可是,經過電話本查找仍是很費時間的。可是,咱們大腦可以記住的東西是必定的,咱們只能記住本身最熟悉的,而長時間不熟悉的天然就忘記了。
其實,計算機也用到了一樣的一個概念,咱們用緩存來存放之前讀取的數據,而不是直接丟掉,這樣,再次讀取的時候,能夠直接在緩存裏面取,而不用再從新查找一遍,這樣系統的反應能力會有很大提升。可是,當咱們讀取的個數特別大的時候,咱們不可能把全部已經讀取的數據都放在緩存裏,畢竟內存大小是必定的,咱們通常把最近常讀取的放在緩存裏(至關於咱們把最近聯繫的朋友的姓名和電話放在大腦裏同樣)。如今,咱們就來研究這樣一種緩存機制。
LRU緩存:
LRU緩存利用了這樣的一種思想。LRU是Least Recently Used 的縮寫,翻譯過來就是「最近最少使用」,也就是說,LRU緩存把最近最少使用的數據移除,讓給最新讀取的數據。而每每最常讀取的,也是讀取次數最多的,因此,利用LRU緩存,咱們可以提升系統的performance.
實現:
要實現LRU緩存,咱們首先要用到一個類 LinkedHashMap。 用這個類有兩大好處:一是它自己已經實現了按照訪問順序的存儲,也就是說,最近讀取的會放在最前面,最最不常讀取的會放在最後(固然,它也能夠實現按照插入順序存儲)。第二,LinkedHashMap自己有一個方法用於判斷是否須要移除最不常讀取的數,可是,原始方法默認不須要移除(這是,LinkedHashMap至關於一個linkedlist),因此,咱們須要override這樣一個方法,使得當緩存裏存放的數據個數超過規定個數後,就把最不經常使用的移除掉。LinkedHashMap的API寫得很清楚,推薦你們能夠先讀一下。
要基於LinkedHashMap來實現LRU緩存,咱們能夠選擇inheritance, 也能夠選擇 delegation, 我更喜歡delegation。基於delegation的實現已經有人寫出來了,並且寫得很漂亮,我就不班門弄斧了。代碼以下:
在博客 http://gogole.iteye.com/blog/692103 裏,做者使用的是雙鏈表 + hashtable 的方式實現的。若是在面試題裏考到如何實現LRU,考官通常會要求使用雙鏈表 + hashtable 的方式。 因此,我把原文的部份內容摘抄以下:
雙鏈表 + hashtable實現原理:
將Cache的全部位置都用雙連錶鏈接起來,當一個位置被命中以後,就將經過調整鏈表的指向,將該位置調整到鏈表頭的位置,新加入的Cache直接加到鏈表頭中。這樣,在屢次進行Cache操做後,最近被命中的,就會被向鏈表頭方向移動,而沒有命中的,而想鏈表後面移動,鏈表尾則表示最近最少使用的Cache。當須要替換內容時候,鏈表的最後位置就是最少被命中的位置,咱們只須要淘汰鏈表最後的部分便可。