LinkedHashMap源碼解析 原

前言

HashMap中的元素時無序的,也就是說遍歷HashMap的時候,順序和放入的順序是不一樣的。 如果需要有序的Map,就可以採用LinkedHashMap.

LinkedHashMap通過維護一個包含所有元素的雙向鏈表,保證了元素迭代的順序。該迭代順序可以是插入順序或者是訪問順序。

LinkedHashMap

注意: 以下的源碼分析基於jdk1.7.

LinkedHashMap定義

LinkedHashMap 繼承了HashMap, 也就是說LinkedHashMap的操作大部分和HashMap是相同的,只是有部分區別。

LinkedHashMap的屬性相比於HashMap,多了兩個屬性:

header: 雙端鏈表的頭節點;(LinkedHashMap通過這個頭節點來維護插入元素的先後順序)
accessOrder: 迭代Map的順序.true表示訪問順序,false表示插入順序;默認是false。

LinkedHashMap.Entry

相比於hashMap.Entry多了兩個屬性:before,after.

before、After是用於維護Entry在雙端鏈表中的位置的。

LinkedHashMap的初始化

當new 一個LinkedHashMap,查看其構造方法(調用了父類HashMap的構造方法),構造方法中執行了LinkedHashMap重寫的init方法:

init方法中初始化了雙端鏈表的頭節點。

LinkedHashMap的put方法

下面跟蹤put方法,來看LinkedHashMap的插入邏輯。 首先執行父類HashMap的put()方法。

再看LinkedHashMap的addEntry方法:

先調用了父類HashMap的addEntry方法,進入HashMap的addEntry方法:

再進入LinkedHashMap的createEntry方法:

因此,如果連續插入三個entry,那麼LinkedHashMap中的雙端鏈表結構如下:

再看inkedHashMap的addEntry方法的後半段代碼:

LinkedHashMap的get方法

查看LinkedHashMap的get方法:

這裏面會調用LinkedHashMap.Entry的recordAccess方法,目的是根據acceessOrder(訪問順序) 來調整鏈表的位置。

調整鏈表的圖示:

可見,最新被訪問的元素,被放到了雙端鏈表的最後面,也就是說,遍歷的時候,這個元素會最後遍歷。

LinkedHashMap的遍歷方式

查看對應的Iterator類的nextEntry方法:

可知,LinkedHashMap遍歷時是從head.after開始遍歷,一直不斷向後遍歷after節點,來實現的。

總結

LinkedHashMap的遍歷方式

1.按照插入順序遍歷;
2.按照最少訪問順序遍歷。(也就是說剛被訪問的元素被插入到鏈表最後面,最後遍歷出來,而最老的元素最先遍歷出來)

利用LinkedHashMap實現LRU算法

LRU算法

LRU(Least Recently Used),即最近最少使用的意思。

LRU算法的設計原則是:如果一個數據在最近一段時間沒有被訪問到,那麼在將來它被訪問的可能性也很小。也就是說,當限定的空間已存滿數據時,應當把最久沒有被訪問到的數據淘汰。

利用LinkedHashMap實現LRU算法

前面的源碼分析中,在put元素的時候,LinkedHashMap中有一個removeEldestEntry方法,支持擴展,按照某種方式,在插入的時候,去移除最老的key, 因此根據LinkedHashMap的這個特性,可以實現LRU算法。 實現方式如下:

/**
 * 實現一個簡單版的LRU算法
 * 
 * @author leiqian
 *
 */
public class LRUCache<K, V> extends LinkedHashMap<K, V> {

    private final int CACHE_SIZE; // 緩存大小

    public LRUCache(int cacheSize) {
        super((int) Math.ceil(cacheSize / 0.75) + 1, 0.75f, true);
        CACHE_SIZE = cacheSize;
    }

    @Override
    protected boolean removeEldestEntry(Map.Entry eldest) {
        return size() > CACHE_SIZE; // 當map中的數據量大於指定的緩存個數的時候,就自動刪除最老的數據
    }

}

測試:

/**
 * 測試LRUCache
 * 
 * @author leiqian
 *
 */
public class LRUCacheTest {

    public static void print(Map<String, String> map) {
        System.out.println("-----------------------");
        for (Map.Entry<String, String> entry : map.entrySet()) {
            System.out.println(entry.getKey() + "," + entry.getValue());
        }
    }

    public static void main(String[] args) {
        LRUCache<String, String> map = new LRUCache<>(5);

        map.put("k1", "v1");
        map.put("k2", "v2");
        map.put("k3", "v3");
        map.put("k4", "v4");
        map.put("k5", "v5");
        map.put("k6", "v6");
        print(map); // 應該移除了k1

        map.get("k2");
        map.put("k7", "v7");
        print(map); // 應該移除了k3

        map.get("k4");
        map.put("k8", "v8");
        print(map); // 應該移除了k5

    }
}

另外,很多優秀的第三方框架都實現了LRUCache,可以看一下他們的源碼中是怎麼實現LRUCache的:

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