LRU算法

LRU是Least Recently Used 的縮寫,即“最近最少使用”,基於LRU算法實現的Cache機制簡單地說就是緩存一定量的數據,當超過設定的閾值時就把一些過期的數據刪除掉,比如我們緩存10000條數據,當數據小於10000時可以隨意添加,當超過10000時就需要把新的數據添加進來,同時要把過期數據刪除,以確保我們最大緩存10000條,那怎麼確定刪除哪條過期數據呢,採用LRU算法實現就是將最老的數據刪除。Java裏面實現LRU緩存通常有兩種選擇,一種是使用LinkedHashMap,一種是自己設計數據結構,使用鏈表+HashMap方式。

LinkedHashMap自身已經實現了順序存儲,默認情況下是按照元素的添加順序存儲,也可以啓用按照訪問順序存儲,即最近讀取的數據放在最前面,最早讀取的數據放在最後面,然後它還有一個判斷是否刪除最老數據的方法,默認是返回false,即不刪除數據。

代碼清單4-4所示示例是LinkedHashMap的一個構造函數,當參數accessOrder爲true時,將會按照訪問順序排序,最後訪問的放在最前,最早訪問的放在後面。

代碼清單4-4 LRU Cache的LinkedHashMap實現

public LinkedHashMap(int initialCapacity, float loadFactor, booleanaccessOrder) {

        super(initialCapacity, loadFactor);

        this.accessOrder = accessOrder;

}

代碼清單4-5所示賽馬是LinkedHashMap自帶的判斷方法,判斷是否刪除最老的元素方法,默認返回false,即不刪除老數據,我們要做的就是重寫這個方法,當滿足一定條件時刪除老數據。

代碼清單4-5 重寫刪除方法

protected booleanremoveEldestEntry(Map.Entry<K,V> eldest) {

        return false;

}

採用inheritance方式實現比較簡單,該方式實現了Map接口,在多線程環境使用時可以使用Collections.synchronizedMap()方法實現線程安全操作。

代碼清單4-6 LRU緩存LinkedHashMap(inheritance)實現

importjava.util.LinkedHashMap;

import java.util.Map;

 

public classLRUCache2<K, V> extends LinkedHashMap<K, V> {

    private final int MAX_CACHE_SIZE;

 

    public LRUCache2(int cacheSize) {

        super((int) Math.ceil(cacheSize / 0.75)+ 1, 0.75f, true);

        MAX_CACHE_SIZE = cacheSize;

    }

 

    @Override

    protected booleanremoveEldestEntry(Map.Entry eldest) {

        return size() > MAX_CACHE_SIZE;

    }

 

    @Override

    public String toString() {

        StringBuilder sb = new StringBuilder();

        for (Map.Entry<K, V> entry :entrySet()) {

            sb.append(String.format("%s:%s", entry.getKey(), entry.getValue()));

        }

        return sb.toString();

    }

}

代碼清單4-6的實現是比較標準的實現,在實際使用過程中這樣寫還是有些煩瑣,更實用的方法是像代碼清單4-7這樣寫,省去了單獨新建一個類的麻煩。

代碼清單4-7 LRU緩存LinkedHashMap(inheritance)實現改進版

final int cacheSize =100;

Map<String,String> map = new LinkedHashMap<String, String>((int) Math.ceil(cacheSize/ 0.75f) + 1, 0.75f, true) {

    @Override

    protected booleanremoveEldestEntry(Map.Entry<String, String> eldest) {

    return size() > cacheSize;

    }

};

相比inheritance實現方式來說,delegation實現方式實現更加優雅一些,但是由於沒有實現Map接口,所以線程同步就需要自己搞定了。

代碼清單4-8 LRU緩存LinkedHashMap(delegation)實現

importjava.util.LinkedHashMap;

import java.util.Map;

import java.util.Set;

 

/**

 * Created by liuzhao on 14-5-13.

 */

public classLRUCache3<K, V> {

 

    private final int MAX_CACHE_SIZE;

    private final float DEFAULT_LOAD_FACTOR =0.75f;

    LinkedHashMap<K, V> map;

 

    public LRUCache3(int cacheSize) {

        MAX_CACHE_SIZE = cacheSize;

        //根據cacheSize和加載因子計算hashmap的capactiy,+1確保當達到cacheSize上限時不會觸發hashmap的擴容,

        int capacity = (int)Math.ceil(MAX_CACHE_SIZE / DEFAULT_LOAD_FACTOR) + 1;

        map = new LinkedHashMap(capacity,DEFAULT_LOAD_FACTOR, true) {

            @Override

            protected booleanremoveEldestEntry(Map.Entry eldest) {

                return size() >MAX_CACHE_SIZE;

            }

        };

    }

 

    public synchronized void put(K key, Vvalue) {

        map.put(key, value);

    }

 

    public synchronized V get(K key) {

        return map.get(key);

    }

 

    public synchronized void remove(K key) {

        map.remove(key);

    }

 

    public synchronized Set<Map.Entry<K,V>> getAll() {

        return map.entrySet();

    }

 

    public synchronized int size() {

        return map.size();

    }

 

    public synchronized void clear() {

        map.clear();

    }

 

    @Override

    public String toString() {

        StringBuilder sb = new StringBuilder();

        for (Map.Entry entry : map.entrySet()){

            sb.append(String.format("%s:%s", entry.getKey(), entry.getValue()));

        }

        return sb.toString();

    }

}

注意,上面的實現方式是非線程安全的,若在多線程環境下使用需要在相關方法上添加synchronized以實現線程安全操作。

前面說過,除了LinkedHashMap方式以外,我們還有一種採用LRU Cache的鏈表+HashMap實現的方式,如代碼清單4-9包含的代碼所示。

代碼清單4-9 LRU Cache的鏈表+HashMap實現

importjava.util.HashMap;

public classLRUCache1<K, V> {

 

    private final int MAX_CACHE_SIZE;

    private Entry first;

    private Entry last;

 

    private HashMap<K, Entry<K, V>>hashMap;

 

    public LRUCache1(int cacheSize) {

        MAX_CACHE_SIZE = cacheSize;

        hashMap = new HashMap<K, Entry<K,V>>();

    }

 

    public void put(K key, V value) {

        Entry entry = getEntry(key);

        if (entry == null) {

            if (hashMap.size() >=MAX_CACHE_SIZE) {

                hashMap.remove(last.key);

                removeLast();

            }

            entry = new Entry();

            entry.key = key;

        }

        entry.value = value;

        moveToFirst(entry);

        hashMap.put(key, entry);

    }

 

    public V get(K key) {

        Entry<K, V> entry =getEntry(key);

        if (entry == null) return null;

        moveToFirst(entry);

        return entry.value;

    }

 

    public void remove(K key) {

        Entry entry = getEntry(key);

        if (entry != null) {

            if (entry.pre != null)entry.pre.next = entry.next;

            if (entry.next != null)entry.next.pre = entry.pre;

            if (entry == first) first =entry.next;

            if (entry == last) last =entry.pre;

        }

        hashMap.remove(key);

    }

 

    private void moveToFirst(Entry entry) {

       if (entry == first) return;

        if (entry.pre != null) entry.pre.next =entry.next;

        if (entry.next != null) entry.next.pre= entry.pre;

        if (entry == last) last = last.pre;

 

        if (first == null || last == null) {

            first = last = entry;

            return;

        }

 

        entry.next = first;

        first.pre = entry;

        first = entry;

        entry.pre = null;

    }

 

    private void removeLast() {

        if (last != null) {

            last = last.pre;

            if (last == null) first = null;

            else last.next = null;

        }

    }

 

 

    private Entry<K, V> getEntry(K key) {

        return hashMap.get(key);

    }

 

    @Override

    public String toString() {

        StringBuilder sb = new StringBuilder();

        Entry entry = first;

        while (entry != null) {

            sb.append(String.format("%s:%s", entry.key, entry.value));

            entry = entry.next;

        }

        return sb.toString();

    }

 

    class Entry<K, V> {

        public Entry pre;

        public Entry next;

        public K key;

        public V value;

    }

}

歡迎關注麥克叔叔每晚十點說,讓我們一起交流與學習。

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