使用LinkedHashMap構建LRU的Cache

這段時間好好整理了一下基礎,發現很多對我來說新的東西,裏面博大精深的東西真的很多,經常使用HashMap,對HashMap的結構和原理非常瞭解,但是忽略了還有LinkedHashMap這個好東西。

 

先轉一篇blog:

 

LinkedHashMap的特性:
Linked內部含有一個private transient Entry header;來記錄元素插入的順序或者是元素被訪問的順序。利用這個線性結構的對象,可以幫助記錄entry加入的前後順序或者記錄entry被訪問的 頻率(最少被訪問的entry靠前,最近訪問的entry靠後)。大致的過程如下:

new LinkedHashMap(10, 0.75, true);
其中前面兩個參數就是HashMap構造函數需要的參數,後面的true表明LinkedHashMap按照訪問的次序來排序。
按照訪問的次序來排序的含義:當調用LinkedHashMap的get(key)或者put(key, value)時,碰巧key在map中被包含,那麼LinkedHashMap會將key對象的entry放在線性結構的最後。
按照插入順序來排序的含義:調用get(key), 或者put(key, value)並不會對線性結構產生任何的影響。

正是因爲LinkedHashMap提供按照訪問的次序來排序的功能,所以它才需要改寫HashMap的get(key)方法(HashMap不需要排序)和HashMap.Entry的recordAccess(HashMap)方法
public Object get(Object key) {
        Entry e = (Entry)getEntry(key);
        if (e == null)
            return null;
        e.recordAccess(this);
        return e.value;
    }

void recordAccess(HashMap m) {
            LinkedHashMap lm = (LinkedHashMap)m;
            if (lm.accessOrder) {
                lm.modCount++;
                remove();
                addBefore(lm.header);
            }
        }
注 意addBefore(lm.header)是將該entry放在header線性表的最後。(參考LinkedHashMap.Entry extends HashMap.Entry 比起HashMap.Entry多了before, after兩個域,是雙向的)

至於put(key, value)方法, LinkedHashMap不需要去改寫,用HashMap的就可以了,因爲HashMap在其put(key, value)方法裏邊已經預留了e.recordAccess(this);

還有一個方法值得關注:
    protected boolean removeEldestEntry(Map.Entry eldest) {
        return false;
    }
當 調用put(key, value)的時候,HashMap判斷是否要自動增加map的size的作法是判斷是否超過threshold, LinkedHashMap則進行了擴展,如果removeEldestEntry方法return false;(默認的實現),那麼LinkedHashMap跟HashMap處理擴容的方式一致;如果removeEldestEntry返回 true,那麼LinkedHashMap會自動刪掉最不常用的那個entry(也就是header線性表最前面的那個)。

這會造成嚴重的性能問題嗎?答案當然是否定的。因爲在這兒的鏈表操作是常量級的。這也是LinkedHashMap/Set在這兒比TreeMap/Set性能更高的原因。

同樣,LinkedHashMap/Set也不是thread-safe的。如果在多線程下訪問,是需要進行外部同步,或者使用Collections.synchronizedMap()的方法包裝成一個thread-safe的Map/Set。

特別需要注意的是,在使用“訪問順序”時,讀取節點操作也是“結構變化”的操作。因爲,這會改變元素遍歷的順序。所以,在使用 LinkedHashMap的iterator()方法,遍歷元素時,如果其它線程有讀取操作,也要進行同步。否則,也會拋出同其它fail-fast一 樣的由於刪除或增加操作而引起的CurrentModificationException的例外。
最後,LinkedHashMap缺省是使用插入順序的,如何構造一個訪問順序的LinkedHashMap呢?很簡單: public LinkedHashMap(int initialCapacity, float loadFactor, boolean accessOrder) accessOrder = true 即可。

 

回來補充一個利用LinkedHashMap來實現LRU的Cache類,看了上面的特性,實現起來實在太簡單了!

 

package lru;

import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Map.Entry;

/**
 *
 *<p>Test</p>
 *<p>Description:</P>
 *<p>Company:Cisco CAS</p>
 *<p>Department:CAS</p>
 *@Author: Tommy Zhou
 *@Since: 1.0
 *@Version:Date:2011-5-13
 *
 **/

public class LRUCache<K,V> extends LinkedHashMap<K, V>{
    private LinkedHashMap<K,V>  cache =null ;
    private int cacheSize = 0;
      
    public LRUCache(int cacheSize){
        this.cacheSize = cacheSize;
        int hashTableCapacity = (int) Math.ceil (cacheSize / 0.75f) + 1;
        cache = new LinkedHashMap<K, V>(hashTableCapacity, 0.75f,true)
        {
            // (an anonymous inner class)
            private static final long serialVersionUID = 1;

            @Override
            protected boolean removeEldestEntry (Map.Entry<K, V> eldest)
            {
                System.out.println("size="+size());
                return size () > LRUCache.this.cacheSize;
            }
        };
    }
  
    public V put(K key,V value){
        return cache.put(key, value);
    }

    public V get(Object key){
        return cache.get(key);
    }
   
    public static void main(String[] args) {
        LRUCache<String, String> lruCache = new LRUCache<String, String>(5);
        lruCache.put("1", "1");
        lruCache.put("2", "2");
        lruCache.put("3", "3");
        lruCache.put("4", "4");
       
        System.out.println(lruCache.get("2"));
        lruCache.get("2");
        lruCache.put("6", "6");
        lruCache.put("5", "5");
        System.out.println(lruCache.get("1"));
       
       
       
       
    }

}

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