如何設計一個LRU Cache?

 如何設計一個LRU Cache?

Google和百度的面試題都出現了設計一個Cache的題目,什麼是Cache,如何設計簡單的Cache,通過蒐集資料,本文給出個總結。

 通常的問題描述可以是這樣:

Question:

[1] Design a layer in front of a system which cache the last n requests and the responses to them from the system.

在一個系統之上設計一個Cache,緩存最近的n個請求以及系統的響應。
what data structure would you use to implement the cache in the later to support following operations.

用什麼樣的數據結構設計這個Cache才能滿足下面的操作呢?
[a] When a request comes look it up in the cache and if it hits then return the response from here and do not pass the request to the system
[b] If the request is not found in the cache then pass it on to the system
[c] Since cache can only store the last n requests, Insert the n+1th request in the cache and delete one of the older requests from the cache

因爲Cache只緩存最新的n個請求,向Cache插入第n+1個請求時,從Cache中刪除最舊的請求。

[d]Design one cache such that all operations can be done in O(1) – lookup, delete and insert.

 Cache簡介:

Cache(高速緩存), 一個在計算機中幾乎隨時接觸的概念。CPU中Cache能極大提高存取數據和指令的時間,讓整個存儲器(Cache+內存)既有Cache的高速度,又能有內存的大容量;操作系統中的內存page中使用的Cache能使得頻繁讀取的內存磁盤文件較少的被置換出內存,從而提高訪問速度;數據庫中數據查詢也用到Cache來提高效率;即便是Powerbuilder的DataWindow數據處理也用到了Cache的類似設計。Cache的算法設計常見的有FIFO(first in first out)和LRU(least recently used)。根據題目的要求,顯然是要設計一個LRU的Cache。

 解題思路:

Cache中的存儲空間往往是有限的,當Cache中的存儲塊被用完,而需要把新的數據Load進Cache的時候,我們就需要設計一種良好的算法來完成數據塊的替換。LRU的思想是基於“最近用到的數據被重用的概率比較早用到的大的多”這個設計規則來實現的。

爲了能夠快速刪除最久沒有訪問的數據項和插入最新的數據項,我們雙向鏈表連接Cache中的數據項,並且保證鏈表維持數據項從最近訪問到最舊訪問的順序。每次數據項被查詢到時,都將此數據項移動到鏈表頭部(O(1)的時間複雜度)。這樣,在進行過多次查找操作後,最近被使用過的內容就向鏈表的頭移動,而沒有被使用的內容就向鏈表的後面移動。當需要替換時,鏈表最後的位置就是最近最少被使用的數據項,我們只需要將最新的數據項放在鏈表頭部,當Cache滿時,淘汰鏈表最後的位置就是了。

  注: 對於雙向鏈表的使用,基於兩個考慮。首先是Cache中塊的命中可能是隨機的,和Load進來的順序無關。其次,雙向鏈表插入、刪除很快,可以靈活的調整相互間的次序,時間複雜度爲O(1)。

    查找一個鏈表中元素的時間複雜度是O(n),每次命中的時候,我們就需要花費O(n)的時間來進行查找,如果不添加其他的數據結構,這個就是我們能實現的最高效率了。目前看來,整個算法的瓶頸就是在查找這裏了,怎麼樣才能提高查找的效率呢?Hash表,對,就是它,數據結構中之所以有它,就是因爲它的查找時間複雜度是O(1)。

梳理一下思路:對於Cache的每個數據塊,我們設計一個數據結構來儲存Cache塊的內容,並實現一個雙向鏈表,其中屬性next和prev時雙向鏈表的兩個指針,key用於存儲對象的鍵值,value用戶存儲要cache塊對象本身。

 Cache的接口:

查詢:

  • 根據鍵值查詢hashmap,若命中,則返回節點,否則返回null。
  • 從雙向鏈表中刪除命中的節點,將其重新插入到表頭。
  • 所有操作的複雜度均爲O(1)。

插入:

  • 將新的節點關聯到Hashmap
  • 如果Cache滿了,刪除雙向鏈表的尾節點,同時刪除Hashmap對應的記錄
  • 將新的節點插入到雙向鏈表中頭部

更新:

  • 和查詢相似

刪除:

  • 從雙向鏈表和Hashmap中同時刪除對應的記錄。

LRU Cache的Java 實現:

 public interface Cache<K extends Comparable, V> {

   V get(K obj);  //查詢

   void put(K key, V obj); //插入和更新

   void put(K key, V obj, long validTime);

   void remove(K key); //刪除

   Pair[] getAll();

   int size();

}

  public class Pair<K extends Comparable, V> implements Comparable<Pair> {

   public Pair(K key1, V value1) {

      this.key = key1;

      this.value = value1;

   }

   public K key;

   public V value;

   public boolean equals(Object obj) {

      if(obj instanceof Pair) {

         Pair p = (Pair)obj;

         return key.equals(p.key)&&value.equals(p.value);

      }

      return false;

   }

   @SuppressWarnings("unchecked")

   public int compareTo(Pair p) {

      int v = key.compareTo(p.key);

      if(v==0) {

         if(p.value instanceof Comparable) {

            return ((Comparable)value).compareTo(p.value);

         }

      }

      return v;

   }

   @Override

   public int hashCode() {

      return key.hashCode()^value.hashCode();

   }

   @Override

   public String toString() {

      return key+": "+value;

   }

}

 public class LRUCache<K extends Comparable, V> implements Cache<K, V>,

      Serializable {

   private static final long serialVersionUID = 3674312987828041877L;

   Map<K, Item> m_map = Collections.synchronizedMap(new HashMap<K, Item>());

   Item m_start = new Item();      //表頭

   Item m_end = new Item();        //表尾

   int m_maxSize;

   Object m_listLock = new Object();        //用於併發的鎖

   static class Item {

      public Item(Comparable k, Object v, long e) {

         key = k;

         value = v;

         expires = e;

      }

      public Item() {}

      public Comparable key;        //鍵值

      public Object value;          //對象

       public long expires;          //有效期

      public Item previous;

      public Item next;

   }

   void removeItem(Item item) {

      synchronized(m_listLock) {

         item.previous.next = item.next;

         item.next.previous = item.previous;

      }

   }

   void insertHead(Item item) {

      synchronized(m_listLock) {

         item.previous = m_start;

         item.next = m_start.next;

         m_start.next.previous = item;

         m_start.next = item;

      }

   }

   void moveToHead(Item item) {

      synchronized(m_listLock) {

         item.previous.next = item.next;

         item.next.previous = item.previous;

         item.previous = m_start;

         item.next = m_start.next;

         m_start.next.previous = item;

         m_start.next = item;

      }

   }

   public LRUCache(int maxObjects) {

      m_maxSize = maxObjects;

      m_start.next = m_end;

      m_end.previous = m_start;

   }

   @SuppressWarnings("unchecked")

   public Pair[] getAll() {

      Pair p[] = new Pair[m_maxSize];

      int count = 0;

      synchronized(m_listLock) {

         Item cur = m_start.next;

         while(cur!=m_end) {

            p[count] = new Pair(cur.key, cur.value);

            ++count;

            cur = cur.next;

         }

      }

      Pair np[] = new Pair[count];

      System.arraycopy(p, 0, np, 0, count);

      return np;

   }

   @SuppressWarnings("unchecked")

   public V get(K key) {

      Item cur = m_map.get(key);

      if(cur==null) {

         return null;

      }

     //過期則刪除對象

      if(System.currentTimeMillis()>cur.expires) {

         m_map.remove(cur.key);

         removeItem(cur);

         return null;

      }

      if(cur!=m_start.next) {

         moveToHead(cur);

      }

      return (V)cur.value;

   }

   public void put(K key, V obj) {

      put(key, obj, -1);

   }

   public void put(K key, V value, long validTime) {

      Item cur = m_map.get(key);

      if(cur!=null) {

         cur.value = value;

         if(validTime>0) {

            cur.expires = System.currentTimeMillis()+validTime;

         }

         else {

            cur.expires = Long.MAX_VALUE;

         }

         moveToHead(cur);  //成爲最新的對象,移動到頭部

         return;

      }

      if(m_map.size()>=m_maxSize) {

         cur = m_end.previous;

         m_map.remove(cur.key);

         removeItem(cur);

      }

      long expires=0;

      if(validTime>0) {

         expires = System.currentTimeMillis()+validTime;

      }

      else {

         expires = Long.MAX_VALUE;

      }

      Item item = new Item(key, value, expires);

      insertHead(item);

      m_map.put(key, item);

   }

   public void remove(K key) {

      Item cur = m_map.get(key);

      if(cur==null) {

         return;

      }

      m_map.remove(key);

      removeItem(cur);

   }

   public int size() {

      return m_map.size();

   }

}

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