1. 程式人生 > >緩存淘汰算法之LRU實現

緩存淘汰算法之LRU實現

性能 current 實現簡單 fault 數量 進行 strong stat xtend

Java中最簡單的LRU算法實現,就是利用 LinkedHashMap,覆寫其中的removeEldestEntry(Map.Entry)方法即可

如果你去看LinkedHashMap的源碼可知,LRU算法是通過雙向鏈表來實現,當某個位置被命中,通過調整鏈表的指向將該位置調整到頭位置,新加入的內容直接放在鏈表頭, 如此一來,最近被命中的內容就向鏈表頭移動,需要替換時,鏈表最後的位置就是最近最少使用的位置。
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedHashMap;
import
java.util.Map; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; /** * LinkedHashMap實現簡單的緩存, 必須實現removeEldestEntry方法,具體參見JDK文檔 * @author * 2017年9月1日 */ public class LRULinkedHashMap<K, V> extends LinkedHashMap<K, V> { private final int
maxCapacity; private static final float DEFAULT_LOAD_FACTOR = 0.75f; private final Lock lock = new ReentrantLock(); public LRULinkedHashMap(int maxCapacity) { super(maxCapacity, DEFAULT_LOAD_FACTOR, true); this.maxCapacity = maxCapacity; } @Override
protected boolean removeEldestEntry(java.util.Map.Entry<K, V> eldest) { return size() > maxCapacity; } @Override public boolean containsKey(Object key) { try { lock.lock(); return super.containsKey(key); } finally { lock.unlock(); } } @Override public V get(Object key) { try { lock.lock(); return super.get(key); } finally { lock.unlock(); } } @Override public V put(K key, V value) { try { lock.lock(); return super.put(key, value); } finally { lock.unlock(); } } public int size() { try { lock.lock(); return super.size(); } finally { lock.unlock(); } } public void clear() { try { lock.lock(); super.clear(); } finally { lock.unlock(); } } public Collection<Map.Entry<K, V>> getAll() { try { lock.lock(); return new ArrayList<Map.Entry<K, V>>(super.entrySet()); } finally { lock.unlock(); } } }

基於雙鏈表的LRU實現

傳統意義的LRU算法是為每一個Cache對象設置一個計數器,每次Cache命中則給計數器+1,而Cache用完,需要淘汰舊內容,放置新內容時,就查看所有的計數器,並將最少使用的內容替換掉。

它的弊端很明顯,如果Cache的數量少,問題不會很大, 但是如果Cache的空間過大,達到10W或者100W以上,一旦需要淘汰,則需要遍歷所有計算器,其性能與資源消耗是巨大的。效率也就非常的慢了。

它的原理: 將Cache的所有位置都用雙連表連接起來,當一個位置被命中之後,就將通過調整鏈表的指向,將該位置調整到鏈表頭的位置,新加入的Cache直接加到鏈表頭中。

這樣,在多次進行Cache操作後,最近被命中的,就會被向鏈表頭方向移動,而沒有命中的,而想鏈表後面移動,鏈表尾則表示最近最少使用的Cache。

當需要替換內容時候,鏈表的最後位置就是最少被命中的位置,我們只需要淘汰鏈表最後的部分即可。

import java.util.Hashtable;

public class LRUCache {  
   
    class CacheNode {  
        CacheNode prev;//前一節點  
        CacheNode next;//後一節點  
        Object value;//
        Object key;//
        CacheNode() {  
        }  
    }  
    
    private int cacheSize;  
    private Hashtable nodes;//緩存容器  
    private int currentSize;  
    private CacheNode first;//鏈表頭  
    private CacheNode last;//鏈表尾  
    
    public LRUCache(int i) {  
        currentSize = 0;  
        cacheSize = i;  
        nodes = new Hashtable(i);//緩存容器  
    }  
      
    /** 
     * 獲取緩存中對象 
     * @param key 
     * @return 
     */  
    public Object get(Object key) {  
        CacheNode node = (CacheNode) nodes.get(key);  
        if (node != null) {  
            moveToHead(node);  
            return node.value;  
        } else {  
            return null;  
        }  
    }  
      
    /** 
     * 添加緩存 
     * @param key 
     * @param value 
     */  
    public void put(Object key, Object value) {  
        CacheNode node = (CacheNode) nodes.get(key);  
          
        if (node == null) {  
            //緩存容器是否已經超過大小.  
            if (currentSize >= cacheSize) {  
                if (last != null)//將最少使用的刪除  
                    nodes.remove(last.key);  
                removeLast();  
            } else {  
                currentSize++;  
            }  
              
            node = new CacheNode();  
        }  
        node.value = value;  
        node.key = key;  
        //將最新使用的節點放到鏈表頭,表示最新使用的.  
        moveToHead(node);  
        nodes.put(key, node);  
    }  
    /** 
     * 將緩存刪除 
     * @param key 
     * @return 
     */  
    public Object remove(Object key) {  
        CacheNode node = (CacheNode) nodes.get(key);  
        if (node != null) {  
            if (node.prev != null) {  
                node.prev.next = node.next;  
            }  
            if (node.next != null) {  
                node.next.prev = node.prev;  
            }  
            if (last == node)  
                last = node.prev;  
            if (first == node)  
                first = node.next;  
        }  
        return node;  
    }  
    public void clear() {  
        first = null;  
        last = null;  
    }  
    /** 
     * 刪除鏈表尾部節點 
     *  表示 刪除最少使用的緩存對象 
     */  
    private void removeLast() {  
        //鏈表尾不為空,則將鏈表尾指向null. 刪除連表尾(刪除最少使用的緩存對象)  
        if (last != null) {  
            if (last.prev != null)  
                last.prev.next = null;  
            else  
                first = null;  
            last = last.prev;  
        }  
    }  
      
    /** 
     * 移動到鏈表頭,表示這個節點是最新使用過的 
     * @param node 
     */  
    private void moveToHead(CacheNode node) {  
        if (node == first)  
            return;  
        if (node.prev != null)  
            node.prev.next = node.next;  
        if (node.next != null)  
            node.next.prev = node.prev;  
        if (last == node)  
            last = node.prev;  
        if (first != null) {  
            node.next = first;  
            first.prev = node;  
        }  
        first = node;  
        node.prev = null;  
        if (last == null)  
            last = first;  
    }  
    
}

緩存淘汰算法之LRU實現