LeetCode之LRU Cache 最近最少使用算法 緩存設計

設計並實現最近最久未使用(Least Recently Used)緩存。html

 

題目描述:java

Design and implement a data structure for Least Recently Used (LRU) cache. It should support the following operations: get and set.node

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.mysql

設計並實現最近最久未使用的緩存數據結構,支持 get 和 set 操做.算法

get()-若是 key 存在,返回對應的 value 值,不然返回 -1.sql

set()-插入 key 對應的 value 到緩存中,若是緩存已滿,將最近最久未使用的元素從緩存中移除。緩存

要實現這個設計,咱們先回顧一下大學課堂上的知識。
LRU,即最近最少使用,是操做系統內存管理的一種頁面置換算法,
常見的頁面置換算法,最佳置換算法(OPT,理想置換算法),先進先出置換算法(FIFO),
最近最久未使用算法(LRU),最少使用算法。數據結構


其中,最佳置換算法是一種理想狀況下的頁面置換算法,實際上不可能實現。該算法的基本思想是發生缺頁時,有些頁面在內存中,其中有一頁將很快被訪問(也包含緊接着的下一條指令的那頁),而其餘頁面則可能要到十、100或者1000條指令後纔會被訪問,每一個頁面均可以用在該頁面首次被訪問前所要執行的指令數進行標記。最佳頁面置換算法規定標記最大的頁應該被置換。但當缺頁發生時,操做系統沒法知道各個頁面下一次是在何時被訪問。這個算法沒法實現,但能夠用於對可實現算法的性能進行衡量。性能

另外兩種主要算法,LFU算法-實現緩存,FIFO算法-實現緩存,能夠查看這裏測試

LRU的實現方法有不少,傳統的LRU實現方法:

1.計數器。最簡單的狀況是使每一個頁表項對應一個使用時間字段,並給CPU增長一個邏輯時鐘或計數器。每次存儲訪問,該時鐘都加1。每當訪問一個頁面時,時鐘寄存器的內容就被複制到相應頁表項的使用時間字段中。這樣咱們就能夠始終保留着每一個頁面最後訪問的「時間」。在置換頁面時,選擇該時間值最小的頁面。
2.棧。用一個棧保留頁號。每當訪問一個頁面時,就把它從棧中取出放在棧頂上。這樣一來,棧頂老是放有目前使用最多的頁,而棧底放着目前最少使用的頁。因爲要從棧的中間移走一項,因此要用具備頭尾指針的雙向鏈連起來。

(1)使用 LinkedHashMap實現Lrucache

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 。

(2)使用雙向鏈表實現

JDK中,LinkedHashMap是經過繼承HashMap,維護一個雙向鏈表實現,

當某個Cache位置被命中,經過調整鏈表的指向將該位置調整到頭位置,新加入的內容直接放在鏈表頭,在屢次進行Cache操做後,最近使用的Cache就會向鏈表頭部移動,鏈表尾部就是命中次數最少,最久未使用的Cache。
空間充滿時,移除尾部的數據就能夠了。有幾點須要注意,一個是Key不存在的狀況,一個是緩存設計要求Key惟一。

下面使用雙向鏈表實現LRU Cache,主要是維護一個緩存設定容量,當前容量,以及雙向鏈表的頭尾節點,方便移動和刪除。

import java.util.HashMap; /** * 近期最少使用算法 設計緩存 */ 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)); // // }  }
相關文章
相關標籤/搜索