LRU緩存算法的實現

LRU就是Least Recently Used,即最近最少使用,是一種經常使用的頁面置換算法,將最近長時間未使用的頁面淘汰,其實也很簡單,就是要將不受歡迎的頁面及時淘汰,不讓它佔着茅坑不拉shit,浪費資源。web

LRU是一種常見的頁面置換算法,在計算中,全部的文件操做都要放在內存中進行,然而計算機內存大小是固定的,因此咱們不可能把全部的文件都加載到內存,所以咱們須要制定一種策略對加入到內存中的文件進項選擇。算法

常見的頁面置換算法有以下幾種:數組

  • LRU 最近最久未使用
  • FIFO 先進先出置換算法 相似隊列
  • OPT 最佳置換算法 (理想中存在的)
  • NRU Clock置換算法
  • LFU 最少使用置換算法
  • PBA 頁面緩衝算法

LRU原理

LRU的設計原理就是,當數據在最近一段時間常常被訪問,那麼它在之後也會常常被訪問。這就意味着,若是常常訪問的數據,咱們須要然其可以快速命中,而不常訪問的數據,咱們在容量超出限制內,要將其淘汰。緩存

其核心就是利用棧,進行操做,其中主要有兩項操做,get和put微信

  • get

get時,若棧中有值則將該值的key提到棧頂,沒有時則返回null數據結構

  • put

棧未滿時,若棧中有要put的key,則更新此key對應的value,並將該鍵值提到棧頂,若無要put的key,直接入棧編輯器

棧滿時,若棧中有要put的key,則更新此key對應的value,並將該鍵值提到棧頂;若棧中沒有put的key 時,去掉棧底元素,將put的值入到棧頂flex

  • 解法:維護一個數組,提供 getput 方法,而且限定 max 數量。

使用時,get 能夠標記某個元素是最新使用的,提高它去第一項。put 能夠加入某個key-value,但須要判斷是否已經到最大限制 maxthis

若未到能直接往數組第一項裏插入 若到了最大限制 max,則須要淘汰數據尾端一個元素。spa

LRUCache cache = new LRUCache( 2 /* 緩存容量 */ );

cache.put(11);
cache.put(22);
cache.get(1);       // 返回  1
cache.put(33);    // 該操做會使得密鑰 2 做廢
cache.get(2);       // 返回 -1 (未找到)
cache.put(44);    // 該操做會使得密鑰 1 做廢
cache.get(1);       // 返回 -1 (未找到)
cache.get(3);       // 返回  3
cache.get(4);       // 返回  4

LRU 算法設計

分析上面的操做過程,要讓 put 和 get 方法的時間複雜度爲 O(1),咱們能夠總結出 cache 這個數據結構必要的條件:查找快,插入快,刪除快,有順序之分

由於顯然 cache 必須有順序之分,以區分最近使用的和久未使用的數據;並且咱們要在 cache 中查找鍵是否已存在;若是容量滿了要刪除最後一個數據;每次訪問還要把數據插入到隊頭。

那麼,什麼數據結構同時符合上述條件呢?哈希表查找快,可是數據無固定順序;鏈表有順序之分,插入刪除快,可是查找慢。因此結合一下,造成一種新的數據結構:哈希鏈表

LRU 緩存算法的核心數據結構就是哈希鏈表,雙向鏈表和哈希表的結合體。這個數據結構長這樣:

js 實現

  • 具體代碼 通常的解法,經過維護一個數組,數組項存放了 key-value 鍵值對對象,每次須要遍歷去尋找 key 值所在的數組下標操做。

已經經過 leetCode 146 的檢測。執行用時 : 720 ms。內存消耗 : 58.5 MB。

function LRUCache(capacity{
    this.capacity = capacity;   // 最大限制
    this.cache = [];
};

/**
 * @param {number} key
 * @return {number}
 */

LRUCache.prototype.get = function (key{
    let index = this.cache.findIndex((item) => item.key === key);
    if (index === -1) {
        return -1;
    }
    // 刪除此元素後插入到數組第一項
    let value = this.cache[index].value;
    this.cache.splice(index, 1);
    this.cache.unshift({
        key,
        value,
    });
    return value;
};

/**
 * @param {number} key
 * @param {number} value
 * @return {void}
 */

LRUCache.prototype.put = function (key, value{
    let index = this.cache.findIndex((item) => item.key === key);
    // 想要插入的數據已經存在了,那麼直接提高它就能夠
    if (index > -1) {
        this.cache.splice(index, 1);
    } else if (this.cache.length >= this.capacity) {
        // 若已經到達最大限制,先淘汰一個最久沒有使用的
        this.cache.pop();
    }
    this.cache.unshift({ key, value });
};

上面的作法其實有變種,能夠經過一個對象來存鍵值對,一個數組來存放鍵的順序。

  • 進階要求O(1)

時間複雜度 O(1),那就不能數組遍歷去查找 key 值。能夠用 ES6 的 Map 來解了,由於 Map 既能保持鍵值對,還能記住插入順序。

function LRUCache(capacity{
    this.cache = new Map();
    this.capacity = capacity;  // 最大限制
};

LRUCache.prototype.get = function (key{
    if (this.cache.has(key)) {
        // 存在即更新
        let temp = this.cache.get(key);
        this.cache.delete(key);
        this.cache.set(key, temp);
        return temp;
    }
    return -1;
};

LRUCache.prototype.put = function (key, value{
    if (this.cache.has(key)) {
        // 存在即更新(刪除後加入)
        this.cache.delete(key);
    } else if (this.cache.size >= this.capacity) {
        // 不存在即加入
        // 緩存超過最大值,則移除最近沒有使用的
        this.cache.delete(this.cache.keys().next().value);
    }
    this.cache.set(key, value);
};


本文分享自微信公衆號 - JavaScript忍者祕籍(js-obok)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。

相關文章
相關標籤/搜索