重識java-LinkedHashMap 頂 原

重識java-LinkedHashMap

使用場景

如果需要使用的Map中的key無序,選擇HashMap;如果要求key有序,則選擇TreeMap。 但是選擇TreeMap就會有性能問題,因爲TreeMap的get操作的時間複雜度是O(log(n))的,相比於HashMap的O(1)還是差不少的,LinkedHashMap的出現就是爲了平衡這些因素,使得 能夠以O(1)時間複雜度增加查找元素,又能夠保證key的有序性 此外,LinkedHashMap提供了兩種key的順序:

  • 訪問順序(access order)。非常實用,可以使用這種順序實現LRU(Least Recently Used)緩存
  • 插入順序(insertion orde)。同一key的多次插入,並不會影響其順序

源代碼解讀

類聲明

public class LinkedHashMap<K,V>
    extends HashMap<K,V>
    implements Map<K,V>

繼承自HashMap,此處實現Map接口,用來表明該類是Map系的類。

功能和特點

  1. LinkedHashMap中採用的這種環型雙向鏈表.
  2. key有序,並且get時間複雜度爲O(1)。
  3. 有兩種記錄順序的方式,一種是訪問順序,一種是key插入的順序。

常量

final boolean accessOrder;//true:key的順序爲訪問順序;false:key的順序爲插入式順序。默認爲插入順序
transient LinkedHashMap.Entry<K,V> head;//雙向鏈表頭結點
transient LinkedHashMap.Entry<K,V> tail;//雙向鏈表尾結點

構造函數


public LinkedHashMap(int initialCapacity, float loadFactor) {
    super(initialCapacity, loadFactor);
    //默認爲false,也就是插入順序
    accessOrder = false;
}

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

public LinkedHashMap() {
    super();
    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;
}

節點數據結構

主要基於HashMap的節點數據結構實現。

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

雙向鏈表實現的LinkedHshMap,所以每個節點須在HashMap的基礎上添加指向前繼節點與後繼節點指針:before,after。

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

在最後添加一個節點,需要取LinkedHashMap的末尾節點,

  1. 雙向鏈表爲空(末爲節點爲空),這新添加的既是頭節點,也是尾節點;
  2. 如果不爲空,p的前繼指向原最後一個節點,最後一個節點的後繼指向p。
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;
}

刪除一個節點時,需要把

  1. 前繼節點的後繼指針 指向 要刪除節點的後繼節點
  2. 後繼節點的前繼指針 指向 要刪除節點的前繼節點

核心方法

afterNodeRemoval

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

移除節點後調用的,就是將節點從雙向鏈表中刪除

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

如果用戶定義了removeEldestEntry的規則,那麼便可以執行相應的移除操作。

afterNodeAccess

void afterNodeAccess(Node<K,V> e) { // move node to last
    LinkedHashMap.Entry<K,V> last;
    // 如果定義了accessOrder,那麼就保證最近訪問節點放到最後
    if (accessOrder && (last = tail) != e) {
        LinkedHashMap.Entry<K,V> p =
            (LinkedHashMap.Entry<K,V>)e, b = p.before, a = p.after;
        p.after = null;
		//e的前驅還有節點,e不是頭結點,否則e是頭結點。
        if (b == null)
            head = a;//把頭結點後移一個
        else
            b.after = a;//把中間節點移除
        
        //e是不是tail節點,是tail節點,則使last爲e的前驅
        if (a != null)
            a.before = b;
        else
            last = b;
        
        //鏈表中只有節點e 
        if (last == null)
            head = p;
        else {//將p移到最後
            p.before = last;
            last.after = p;
        }
        tail = p;
        ++modCount;
    }
}

進行put之後,對節點的訪問了,那麼這個時候就會更新鏈表,把最近訪問的放到最後,保證鏈表的key按照訪問有序。

put put函數在LinkedHashMap中未重新實現,只是實現了afterNodeAccessafterNodeInsertion兩個回調函數。

get

public V get(Object key) {
    Node<K,V> e;
    if ((e = getNode(hash(key), key)) == null)
        return null;
    if (accessOrder)
        afterNodeAccess(e);
    return e.value;
}

get函數重新實現並加入了afterNodeAccess來保證訪問順序

總結

  1. 怎樣保證插入順序? 使用前驅和後繼指針,使得原來的HashMap有序,在LinkedHashMap中覆蓋HashMapnewNode 方法,使得每次put數據時,新建的節點都是LinkedHashMap.Entry<K,v> 類型的,比普通的HsahMap.Entry 多一個前驅結點和一個後繼節點,使用前驅和後繼保證插入有序。
  2. 怎麼樣保證訪問順序? 覆蓋父類HashMapafterNodeAccess 方法,使得每次訪問後,都改變鏈表順序。使得原鏈表按訪問排序。將最新一次訪問的節點放到鏈表的最後。

Thanks for reading! want more

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