LinkedHashMap源碼(JDK1.8)

1. 概述

LinkedHashMap繼承了HashMap,並在其基礎上維護了一條雙向鏈表,用來保證順序訪問。

2. 源碼分析

2.1 內部類和屬性

LinkedHashMap的內部類繼承了Node,並根據需要增加了before,after屬性。這兩個屬性你肯定似曾相識,在LinkedList中使用過。其實他們的功能其實是一樣的,定位前一個或後一個entry

// 頭結點
transient LinkedHashMap.Entry<K,V> head;
// 尾節點
transient LinkedHashMap.Entry<K,V> tail;
// 排序方式,true:訪問順序排序(LRU) false:插入順序排序
final boolean accessOrder;

// 其它的屬性都在hashmap中,這裏就不一一列出了

// LinkedHashMap的內容存儲類
static class Entry<K,V> extends HashMap.Node<K,V> {
    // 前後指針
    Entry<K,V> before, after;
    Entry(int hash, K key, V value, Node<K,V> next) {
        super(hash, key, value, next);
    }
}

2.2 構造方法

通過下面的代碼可以看出,LinkedHashMap的構造方法基本是調用父類構造來完成的,自己只是完成accessOrder屬性操作(也就是排序方式,默認false)。

public LinkedHashMap() {
    super();
    accessOrder = false;
}

public LinkedHashMap(int initialCapacity) {
    super(initialCapacity);
    accessOrder = false;
}

public LinkedHashMap(int initialCapacity, float loadFactor) {
    super(initialCapacity, loadFactor);
    accessOrder = false;
}

public LinkedHashMap(Map<? extends K, ? extends V> m) {
    super();
    accessOrder = false;
    putMapEntries(m, false);
}
// 顯式指定是否啓用有序訪問
public LinkedHashMap(int initialCapacity,
                     float loadFactor,
                     boolean accessOrder) {
    super(initialCapacity, loadFactor);
    this.accessOrder = accessOrder;
}

2.3 插入方法和鉤子方法

LinkedHashMap的元素獲取是調用HashMapput方法,不過LinkedHashMap通過重寫HashMap的鉤子方法來實現一些自己的邏輯。

// HashMap
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {
   	…… 省略無關代碼
    if (e != null) { // existing mapping for key
            V oldValue = e.value;
            if (!onlyIfAbsent || oldValue == null)
                e.value = value;
            afterNodeAccess(e);		// 鉤子方法
            return oldValue;
        }
    }
    if (++size > threshold)
        resize();
    afterNodeInsertion(evict);		// 鉤子方法
    return null;
}

// 鉤子方法的定義(在HashMap中爲空實現,留給子類重寫邏輯)
void afterNodeAccess(Node<K,V> p) { }
void afterNodeInsertion(boolean evict) { }
void afterNodeRemoval(Node<K,V> p) { }

// LinkedHashMap
// 節點訪問後,移動元素e到鏈尾,頻繁訪問的元素就會落在尾部
void afterNodeAccess(Node<K,V> e) { // move node to last
    LinkedHashMap.Entry<K,V> last;
    // accessOrder爲null且tail節點不是e,纔會進入方法
    if (accessOrder && (last = tail) != e) {
        LinkedHashMap.Entry<K,V> p =
            (LinkedHashMap.Entry<K,V>)e, 
        // b爲e的前驅節點,a爲p的後繼節點
        b = p.before, a = p.after;
        p.after = null;
        if (b == null)
            head = a;		// b=null說明e爲頭結點,則將後繼a設爲頭節點
        else
            b.after = a;	// b!=null,將b的後繼設置爲a
        if (a != null)
            a.before = b;	// a!=null,將a的前驅設置爲b
        else
            last = b;		// a=null則將last設置爲b
        if (last == null)
            head = p;		// p爲頭結點
        else {
            p.before = last;
            last.after = p;	// 設置p和last的鏈接關係
        }
        tail = p;		// 設置p爲尾節點
        ++modCount;
    }
}

// 在put方法調用到這裏evict爲true
void afterNodeInsertion(boolean evict) { // possibly remove eldest
    LinkedHashMap.Entry<K,V> first;
    if (evict && (first = head) != null && removeEldestEntry(first)) {
        K key = first.key;
        // 作用顯而易見,移除首節點
        removeNode(hash(key), key, null, false, true);	// 在HashMap中,這裏不再解釋
    }
}

// 這個方法總是返回false,你可能會疑惑這個鬼東西是否有問題。
// 其實這個方法是一個鉤子方法,留給你自己來實現決定是否刪除first(LRU算法實現)
protected boolean removeEldestEntry(Map.Entry<K,V> eldest) {
    return false;
}

// 節點刪除後斷開鏈接
void afterNodeRemoval(Node<K,V> e) { // unlink
    LinkedHashMap.Entry<K,V> p =
        (LinkedHashMap.Entry<K,V>)e, b = p.before, a = p.after;
    p.before = p.after = null;
    if (b == null)
        head = a;
    else
        b.after = a;
    if (a == null)
        tail = b;
    else
        a.before = b;
}

2.4 獲取方法

瞭解了hashmapgetNode方法和上邊的鉤子方法,那麼這裏看起來就比較無腦了,沒啥邏輯。

public V get(Object key) {
    Node<K,V> e;
    // 調用hashmap的getNode獲取entry
    if ((e = getNode(hash(key), key)) == null)
        return null;
    // 決定是否更新e到鏈尾
    if (accessOrder)
        afterNodeAccess(e);
    return e.value;
}

2.5 移除方法

移除方法更沒營養,調用HashMapremove方法時調用了一下afterNodeRemoval方法。

2.6 LRU緩存實現

主要內容其實就是重寫LinkedHashMapremoveEldestEntry方法,Mybatis也用LinkedHashMap實現LRU算法,也是這個套路,懂就行了

// 摘抄自石杉的LRU實現
class LRUCache<K, V> extends LinkedHashMap<K, V> {
    private final int CACHE_SIZE;

    /**
     * 傳遞進來最多能緩存多少數據
     *
     * @param cacheSize 緩存大小
     */
    public LRUCache(int cacheSize) {
        // true 表示讓 linkedHashMap 按照訪問順序來進行排序,最近訪問的放在頭部,最老訪問的放在尾部。
        super((int) Math.ceil(cacheSize / 0.75) + 1, 0.75f, true);
        CACHE_SIZE = cacheSize;
    }

    /**
     * 鉤子方法,通過put新增鍵值對的時候,若該方法返回true
     * 便移除該map中最老的鍵和值
     */
    @Override
    protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {
        // 當 map中的數據量大於指定的緩存個數的時候,就自動刪除最老的數據。
        return size() > CACHE_SIZE;
    }
}

3. 總結

總結好難,不總結了。

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