緩存置換算法LRU

一、緩存置換算法介紹

在面試阿里前端的時候,面試官提出LRU(Least recently used,最近最少使用)的問題,開始的時候犯一些方向性的錯誤,在面試官的提示下才想出思路。

解決一個問題得分步驟,第一步也是最重要的一步,先搞懂LRU是什麼,什麼場景下使用LRU?

LRU是什麼?

LRU是內存管理的一種頁面置換算法,選擇最近最久未使用的頁面予以淘汰。

通俗一點,內存最多隻能存放這麼多數據,但是又有新的數據來了,就得從內存裏移除一個,給新數據騰出空間。但是刪除哪個數據呢?不能隨便刪一個吧,所以就得制定一個規矩。常見的算法就有LRU、FIFO、LFU。

LRU(Least recently used,最近最少使用)就是把那個最久沒有使用過的數據移除。

FIFO(First in first out, 先進先出)就是最先存入的數據最先淘汰。

LFU(Least Frequently Used,最近最少使用)就是最近使用頻率最低的被淘汰。

二、算法思路

算法思路如下:

  1. 判斷緩存是否存在,是則轉5,否則轉2
  2. 緩存空間是否已滿,是則轉3,否則轉4
  3. 釋放內存
  4. 存入緩存,轉5
  5. 調用緩存,結束

上述過程如下圖:

三、LRU算法

LRU場景如下

前端緩存假設只能存100個值,當101值存入的時候,就得刪除原先的一個值,然後放入一個新的值。LRU的思想就是,要把最近最少使用的那個值移除。

問題來了,如何找到哪個值是最近最少使用的?

誤區1: 給每個緩存都添加一個時間戳,操作了這個緩存之後更新時間戳,就能找到最近最少使用的緩存了。

誤區2: 使用Map數據結構存放數據,將時間戳作爲鍵,緩存作爲值

上面兩個誤區就是我面試的時候踩的嚴嚴實實的兩個問題,然後大佬幫我分析了一下。

最近最少使用是不是意味着,被調用或者剛存入的緩存就是最新的,然後仔細分析一下存入緩存這個過程:

  1. 最開始緩存爲空,直接存入第一個緩存
  2. 存入第二個緩存的時候,奇蹟發生了,無論是放在第一個前面還是後面,這兩個都是有序的。
  3. 存入第三個時候只要按照前兩個的順序存放,就會保持一個順序延續下去。這種數據結構像啥——鏈表呀。
  4. 根據第二步操作依次添加第三、第四和第五個緩存值,那緩存就是一個鏈表了,假設表頭到表尾就是最新到最久的順序

考慮一下讀取緩存的情況:

  1. 按順序遍歷緩存,命中緩存時執行第二步,未命中時返回null
  2. 移除緩存,然後將緩存添加到鏈表頭部

示意圖和js實現代碼如下:

LRU算法示意圖

function Cache(value, key) {
  this.value = value;
  this.key = key;
}

function LRU() {
  this.localCache = [];
  this.maxLength = 5;
}

LRU.prototype.getCache = function(key) {
  for (var i = 0;i < this.localCache.length; i++) {
    if (this.localCache[i].key === key) {
      var temp = this.localCache.splice(i, 1);
      this.localCache.unshift(temp[0]);
      return temp;
    }
  }

  return null;
}

LRU.prototype.setCache = function(cache) {
  if (this.localCache.length === this.maxLength) {
    this.localCache.pop();
  }

  this.localCache.unshift(cache);
}


var c1 = new Cache('1', {p: 111});
var c2 = new Cache('2', {p: 222});
var c3 = new Cache('3', {p: 333});
var c4 = new Cache('4', {p: 444});
var c5 = new Cache('5', {p: 555});
var c6 = new Cache('6', {p: 666});

var lru = new LRU();

lru.setCache(c1);
lru.setCache(c2);
lru.setCache(c3);
lru.setCache(c4);
lru.setCache(c5);

console.log(lru);

lru.getCache('3');
console.log(lru);

lru.setCache(c6);
console.log(lru);

四、總結

這裏只是對LRU進行簡單的實現,很多地方都可以進行擴展的。比如說,將鍵值與下標放入map,查詢效率直接從O(n)變成O(1)。還可以不使用js自帶的Array對象實現,而是將每個數據節點變成一個帶有前指針和後指針的node。

  • LRU算法其實挺簡單的,不要走入上面提到的兩個誤區,這個問題很容易就能分析出來。
  • FIFO這個就更簡單了,淘汰的時候從一個方向淘汰,被調用的也不需要額外處理。
  • LFU算法就比上面兩個複雜一點,需要多保存一個數據(緩存使用的次數)。

下篇博客,將會補上另外兩種的算法的js實現。

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章