Java集合之LinkedHashMap


LinkedHashMap是HashMap的子類,通過維護一個雙向鏈表,實現Map有序遍歷元素的特性。

因此,對於LinkedHashMap來說,其基本特性如下:

基本特性 結論
元素是否允許為null key和value可以為null
元素是否允許重復 key重復會覆蓋,value可以重復
是否有序
是否線程安全

源碼分析

本文使用的是JDK 1.8.0_201的源碼。

成員變量

LinkedHashMap是繼承HashMap的成員變量,實現有序的特性,還維護了額外幾個成員變量:

成員變量 作用
transient LinkedHashMap.Entry<K,V> head; 鏈表的頭部
transient LinkedHashMap.Entry<K,V> tail; 鏈表的尾部
final boolean accessOrder; 遍歷的模式,默認是false

put操作

LinkedHashMap沒有實現自己的put操作,繼承自HashMap。問題在於,LinkedHashMap是怎么維護元素的插入順序的呢?換句話說,LinkedHashMap的雙向鏈表是在哪里維護的?答案是在HashMap put方法中調用的模板方法newNode()。HashMap中存在許多的模板方法,以方便LinkedHashMap去實現自己的特性。

對於LinkedHashMap來說,newNode()方法實現如下:

Node<K,V> newNode(int hash, K key, V value, Node<K,V> e) {
    // 創建一個Entry
    LinkedHashMap.Entry<K,V> p =
        new LinkedHashMap.Entry<K,V>(hash, key, value, e);
    // 維護雙向鏈表
    linkNodeLast(p);
    return p;
}

private void linkNodeLast(LinkedHashMap.Entry<K,V> p) {
    LinkedHashMap.Entry<K,V> last = tail;
    tail = p;
    if (last == null)
        head = p;
    else {
        p.before = last;
        last.after = p;
    }
}

get操作

LinkedHashMap自己實現了get()方法,代碼如下:

public V get(Object key) {
    Node<K,V> e;
    if ((e = getNode(hash(key), key)) == null)
        return null;
    // 與HashMap的區別在這里
    if (accessOrder)
        afterNodeAccess(e);
    return e.value;
}

上面的代碼與HashMap的區別在於accessOrder條件的判斷,如果accessOrder為true,程序就會去執行afterNodeAccess(e)方法。

LRU緩存實現

accessOrder變量,是LinkedHashMap中一個有趣的屬性。我們先看源碼文檔注釋:

A structural modification is any operation that adds or deletes one or more mappings or, in the case of access-ordered linked hash maps, affects iteration order. In insertion-ordered linked hash maps, merely changing the value associated with a key that is already contained in the map is not a structural modification. In access-ordered linked hash maps, merely querying the map with get is a structural modification.

對於insertion-ordered模式來說,即accessOrder為false的情況,只有添加和刪除操作,才會改變元素的順序。而對於access-ordered模式來說,即accessOrder為true的情況,即使是get()操作,都會改變元素的順序。

access-ordered模式,是實現LRU緩存(最近最少使用)的關鍵。所謂最近最少使用,就是說當緩存滿了,優先淘汰那些最近最少被訪問的數據。

LinkedHashMap的具體實現就是,每次訪問時,都把訪問的數據移到雙向隊列的尾部,那么隊列頭部的元素就是最少被訪問的數據,每次要淘汰數據時,就刪除隊列頭部的數據。

回過頭再看前面get操作中的afterNodeAccess(e)方法,就會發現這個方法正是每次訪問時,把訪問數據移到隊尾的關鍵。

總結

1. LinkedHashMap是有序的嗎?它是如何保證有序的?

LinkedHashMap通過維護一個雙向鏈表,從而保證元素的順序。需要注意的是,LinkedHashMap保證的有序是指元素的插入順序或者訪問順序,而不是指元素字典順序。至於LinkedHashMap具體保證的是插入順序還是訪問順序,通過初始化時accessOrder字段的值來確定。如果accessOrder=true,表示訪問順序,false表示插入順序。

2. 如何使用LinkedHashMap實現一個LRU緩存?

LinkedHashMap有兩種維護順序的模式,insetion-ordered模式跟access-ordered模式,當成員變量accessOrder為true,即access-ordered模式時,每次訪問元素時,就會將元素移到隊列的尾部,這樣頭部的元素就成了最少被訪問的元素。當需要淘汰數據時,頭部的元素先被淘汰。這個特性非常適合用來實現LRU緩存。

至於何時淘汰數據,LinkedHashMap提供了一個模板方法removeEldestEntry()。具體做法如下:

public class LRUCache<K, V> extends LinkedHashMap<K, V> {
    private final int size;

    public LRUCache(int size) {
        super(size, 0.75f, true);
        this.size = size;
    }

    @Override
    protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {
        // 當緩存的元素個數大於設置的大小時,淘汰最老的那個元素
        return this.size() > this.size;
    }
}
3. 為什么LinkedHashMap的遍歷性能會比HashMap好?

因為LinkedHashMap的遍歷是通過雙向鏈表實現的,與size的大小有關,即與map的實際元素個數有關。與此同時,HashMap的遍歷,是需要遍歷底層數組的,舉個例子,一個只存放了一個元素的HashMap,其底層數組的大小至少是16,那么這個遍歷有15次是多余的。由於負載因子的存在(不考慮負載因子大於0的情況),HashMap的遍歷總是會存在多余的次數。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM