1. 程式人生 > 程式設計 >淺談Java如何實現一個基於LRU時間複雜度為O(1)的快取

淺談Java如何實現一個基於LRU時間複雜度為O(1)的快取

LRU:Least Recently Used最近最少使用,當快取容量不足時,先淘汰最近最少使用的資料。就像JVM垃圾回收一樣,希望將存活的物件移動到記憶體的一端,然後清除其餘空間。

快取基本操作就是讀、寫、淘汰刪除。

讀操作時間複雜度為O(1)的那就是hash操作了,可以使用HashMap索引 key。

寫操作時間複雜度為O(1),使用連結串列結構,在連結串列的一端插入節點,是可以完成O(1)操作,但是為了配合讀,還要再次將節點放入HashMap中,put操作最優是O(1),最差是O(n)。

不少童鞋就有疑問了,寫入時又使用map進行了put操作,為何快取不直接使用map?沒錯,首先使用map儲存了節點資料就是採用空間換時間,但是淘汰刪除不好處理,使用map如何去記錄最近最少使用(涉及到時間、頻次問題)。so,使用連結串列可以將活躍節點移動到連結串列的一端,淘汰時直接從另一端進行刪除。

public class LruCache<K,V> {
	/** 這裡簡單點直接初始化了*/
  private int capacity = 2;
  private int size = 0;
  private HashMap<K,DoubleListNode<K,V>> cache = new HashMap<>(capacity);
  private DoubleListNode<K,V> lruNode = new DoubleListNode<K,V>(null,null,null);
  private DoubleListNode<K,V> mruNode = new DoubleListNode<K,null);

  public V get(K key){
    DoubleListNode<K,V> target = cache.get(key);
    if (target == null) {
      return null;
    }
    /** 使用過就移動到右側 */
    move2mru(target);
    return target.value;
  }

  public void put(K key,V value){
    if(cache.containsKey(key)){
      DoubleListNode<K,V> temp = cache.get(key);
      temp.value = value;
      /** 使用過就移動到右側 */
      move2mru(temp);
      return;
    }

		/** 容量滿了清除左側 */
    if(size >= capacity){
      evict4lru();
    }
    DoubleListNode<K,V> newNode = new DoubleListNode<>(mruNode,key,value);
    if(size == 0){
      lruNode.next = newNode;
    }
    mruNode.next = newNode;
    mruNode = newNode;
    cache.put(key,newNode);
    size++;
  }

  private void move2mru(DoubleListNode<K,V> newMru){
    DoubleListNode<K,V> pre = newMru.pre;
    DoubleListNode<K,V> next = newMru.next;
    pre.next = next;
    newMru.pre = mruNode;
    mruNode.next = newMru;
    mruNode = newMru;
  }

  private void evict4lru(){
  	cache.remove(lruNode.next.key);
    lruNode.next = lruNode.next.next;
    size--;
  }

  public String toString(){
    StringBuffer sb = new StringBuffer("lru -> ");
    DoubleListNode<K,V> temp = lruNode;
    while(temp!=null){
      sb.append(temp.key).append(":").append(temp.value);
      sb.append(" -> ");
      temp = temp.next;
    }
    sb.append(" -> mru ");
    return sb.toString();
  }

  public static void main(String[] args) {
    LruCache<String,String> cache = new LruCache<>();
    cache.put("1","1");
    System.out.println(cache);
    cache.get("1");
    cache.put("2","2");
    System.out.println(cache);
    cache.put("3","3");
    System.out.println(cache);
    cache.put("4","4");
    System.out.println(cache);
  }
}

class DoubleListNode<K,V>{
  K key;
  V value;
  DoubleListNode<K,V> pre;
  DoubleListNode<K,V> next;

  public DoubleListNode(K key,V value){
    this.key = key;
    this.value = value;
  }

  public DoubleListNode(DoubleListNode<K,V> pre,V> next,K key,V value){
    this.pre = pre;
    this.next = next;
    this.key = key;
    this.value = value;
  }
}

這裡使用連結串列,及HashMap完成了基於LRU的快取,其中HashMap主要用來快速索引key,連結串列用來完成LRU機制。當然尚有許多不足,包括快取移除remove,快取ttl,執行緒安全等。

到此這篇關於淺談Java如何實現一個基於LRU時間複雜度為O(1)的快取的文章就介紹到這了,更多相關Java基於LRU時間複雜度為O(1)的快取內容請搜尋我們以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援我們!