如何設計一個LRU cache

來源:互聯網
上載者:User

如何設計一個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();   }}

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.