重識java-LinkedHashMap

重識java-LinkedHashMap

使用場景

若是須要使用的Map中的key無序,選擇HashMap;若是要求key有序,則選擇TreeMap。 可是選擇TreeMap就會有性能問題,由於TreeMap的get操做的時間複雜度是O(log(n))的,相比於HashMap的O(1)仍是差很多的,LinkedHashMap的出現就是爲了平衡這些因素,使得 可以以O(1)時間複雜度增長查找元素,又可以保證key的有序性 此外,LinkedHashMap提供了兩種key的順序:java

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

源代碼解讀

類聲明node

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

刪除一個節點時,須要把this

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

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

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

相關文章
相關標籤/搜索