LinkedHashMap構造函數參數詳解


LinkedHashMap 繼承自 HashMap,所以它的底層仍然是基於拉鏈式散列結構。該結構由數組和鏈表+紅黑樹,在此基礎上LinkedHashMap 增加了一條雙向鏈表,保持遍歷順序和插入順序一致的問題。

訪問順序存儲的LinkedHashMap會把get方法對應的Entry節點放置在Entry鏈表表尾。

LinkedHashMap構造函數有3個參數:

public LinkedHashMap(int initialCapacity, float loadFactor, boolean accessOrder)

initialCapacity:是初始數組長度

loadFactor:負載因子,表示數組的元素數量/數組長度超過這個比例,數組就要擴容

accessOrder:false: 基於插入順序(默認)     true:  基於訪問順序

當accessOrder為true,每次get元素的時候,都會去執行 afterNodeAccess 方法,這個方法會將元素重新插入到雙向鏈表的結尾。

//標准的如何在雙向鏈表中將指定元素放入隊尾
// LinkedHashMap 中覆寫
//訪問元素之后的回調方法

/**
 * 1. 使用 get 方法會訪問到節點, 從而觸發調用這個方法
 * 2. 使用 put 方法插入節點, 如果 key 存在, 也算要訪問節點, 從而觸發該方法
 * 3. 只有 accessOrder 是 true 才會調用該方法
 * 4. 這個方法會把訪問到的最后節點重新插入到雙向鏈表結尾
 */
void afterNodeAccess(Node<K,V> e) { // move node to last
    // 用 last 表示插入 e 前的尾節點
    // 插入 e 后 e 是尾節點, 所以也是表示 e 的前一個節點
    LinkedHashMap.Entry<K,V> last;
    //如果是訪問序,且當前節點並不是尾節點
    //將該節點置為雙向鏈表的尾部
    if (accessOrder && (last = tail) != e) {
        // p: 當前節點
        // b: 前一個節點
        // a: 后一個節點
        // 結構為: b <=> p <=> a
        LinkedHashMap.Entry<K,V> p =
            (LinkedHashMap.Entry<K,V>)e, b = p.before, a = p.after;
        // 結構變成: b <=> p <- a
        p.after = null;

        // 如果當前節點 p 本身是頭節點, 那么頭結點要改成 a
        if (b == null)
            head = a;
        // 如果 p 不是頭尾節點, 把前后節點連接, 變成: b -> a
        else
            b.after = a;

        // a 非空, 和 b 連接, 變成: b <- a
        if (a != null)
            a.before = b;
        // 如果 a 為空, 說明 p 是尾節點, b 就是它的前一個節點, 符合 last 的定義
          // 這個 else 沒有意義,因為最開頭if已經確保了p不是尾結點了,自然after不會是null
        else
            last = b;

        // 如果這是空鏈表, p 改成頭結點
        if (last == null)
            head = p;
        // 否則把 p 插入到鏈表尾部
        else {
            p.before = last;
            last.after = p;
        }
        tail = p;
        ++modCount;
    }
}

每當put元素后,都會執行afterNodeInsertion方法,將超出容量的頭結點刪除

    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); } }

如果是自己繼承LinkedhashMap來實現一個LRUCache,則需要重寫removeEldestEntry方法,可以將最早訪問的元素(即雙向鏈表的頭結點)刪除。

    @Override
//移除最近最少被訪問條件之一,通過覆蓋此方法可實現不同策略的緩存
protected boolean removeEldestEntry(Map.Entry<String, String> eldest) { return this.size() > cache_size; }

 

總結

LinkedHashMap在HashMap的基礎上使用一個雙端鏈表維持有序的節點。這個有序並不是通常意義上的大小關系,默認情況下使用的插入順序,意味着新插入的節點被添加到雙端鏈表的尾部,而一旦使用了訪問順序,即accessOrder為true,那么在訪問某一節點時,會將該節點移到雙端鏈表的尾部。正因為此特性,可以在LinkedHashMap中使用三個參數的構造方法並制定accessOrder為true將LinkedHashMap實現為LRU緩存,這樣經常訪問的就會被移到鏈表的尾部,而越少訪問的就在鏈表的頭部。 

由於雙端鏈表維持了所有的節點,所以keySet()、values()以及entrySet()得到的鍵、值、鍵值對都是按照雙端鏈表中的節點順序的。 

另外尤其需要注意的是,在put、get、remove方法中涉及到的雙端鏈表的操作,由於都是引用的更改,所以並沒有影響到HashMap的底層結構:數組+鏈表+紅黑樹。


免責聲明!

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



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