【Java源碼分析】LinkedHashMap源碼分析

類的定義

public class LinkedHashMap<K, V> extends HashMap<K, V> {}
  1. 基於雙向鏈表實現,屬於Map的一類,其父類是HashMap。最主要的是LinkedHashMap可以保證迭代順序。如果既想要使用Map的功能又想要鍵值對插入和取出是有序的,可以使用LinkedHashMap
  2. 鍵和值支持任意類型,包括null
  3. 迭代的順序就是鍵值對被插入Map的順序。如果三個參數的構造方法中accessOrder參數被設置爲true,那麼鍵值對的迭代順序就變爲訪問順序(可以影響訪問的操作是put get putAll),在Android的LRUCache中使用的數據結構就是LinkedHashMap。
  4. 非線程安全的,如果同時有多個線程執行改變Map結構的操作如put或者remove等操作的時候就需要調用者保證線程安全性
  5. 迭代器進行迭代的時候同樣是不允許有修改Map結構的操作,否則拋出ConcurrentModificationException

主要成員變量

// true的時候按訪問順序排列鍵值對,false的時候按插入順序
private final boolean accessOrder;
transient LinkedEntry<K, V> header;

構造函數

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

public LinkedHashMap(int initialCapacity) {
    this(initialCapacity, DEFAULT_LOAD_FACTOR);
}

public LinkedHashMap(int initialCapacity, float loadFactor) {
    this(initialCapacity, loadFactor, false);
}

public LinkedHashMap(
        int initialCapacity, float loadFactor, boolean accessOrder) {
    super(initialCapacity, loadFactor);
    init();
    this.accessOrder = accessOrder;
}

public LinkedHashMap(Map<? extends K, ? extends V> map) {
    this(capacityForInitSize(map.size()));
    constructorPutAll(map);
}

構造函數和前面的集合類差不多,只是這裏多了一個訪問順序的標誌位

鍵值對實體

static class LinkedEntry<K, V> extends HashMapEntry<K, V> {
    LinkedEntry<K, V> nxt;
    LinkedEntry<K, V> prv;

    /** Create the header entry */
    LinkedEntry() {
        super(null, null, 0, null);
        nxt = prv = this;
    }

    /** Create a normal entry */
    LinkedEntry(K key, V value, int hash, HashMapEntry<K, V> next,
                LinkedEntry<K, V> nxt, LinkedEntry<K, V> prv) {
        super(key, value, hash, next);
        this.nxt = nxt;
        this.prv = prv;
    }
}

在HashMap鍵值對的實體上增加了nxt prv指針。除此之外在構造函數中調用了super();方法,該方法在HashMap中用於將映射實體加入到數組中。

返回最早加入的實體

public Entry<K, V> eldest() {
    LinkedEntry<K, V> eldest = header.nxt;
    return eldest != header ? eldest : null;
}

如果Map爲空,那麼返回NULL

添加新的映射實體

@Override void addNewEntry(K key, V value, int hash, int index) {
    LinkedEntry<K, V> header = this.header;

    // Remove eldest entry if instructed to do so.
    LinkedEntry<K, V> eldest = header.nxt;
    if (eldest != header && removeEldestEntry(eldest)) {
        remove(eldest.key);
    }

    // Create new entry, link it on to list, and put it into table
    LinkedEntry<K, V> oldTail = header.prv;
    LinkedEntry<K, V> newTail = new LinkedEntry<K,V>(
            key, value, hash, table[index], header, oldTail);
    table[index] = oldTail.nxt = header.prv = newTail;
}

這個方法在LruCache中會用到,每當添加一個新的實體的時候就刪除最久的那個,是否刪除通過removeEldestEntry(eldest)來判斷,該方法返回true 或者false。方法的後半部分是一個雙向鏈表的增加節點操作,頭節點的nxt指向第一個節點,prv指向最後一個節點。添加新節點的時候主要是修改prv和prv所指向的節點的指針。

添加NULL key的映射實體

@Override void addNewEntryForNullKey(V value) {
    LinkedEntry<K, V> header = this.header;

    // Remove eldest entry if instructed to do so.
    LinkedEntry<K, V> eldest = header.nxt;
    if (eldest != header && removeEldestEntry(eldest)) {
        remove(eldest.key);
    }

    // Create new entry, link it on to list, and put it into table
    LinkedEntry<K, V> oldTail = header.prv;
    LinkedEntry<K, V> newTail = new LinkedEntry<K,V>(
            null, value, 0, null, header, oldTail);
    entryForNullKey = oldTail.nxt = header.prv = newTail;
}

前半部分和上面的添加操作一樣,後面就是倒數第二句不同,沒有設置key和hash,以及table[]

只添加不移除

@Override HashMapEntry<K, V> constructorNewEntry(
        K key, V value, int hash, HashMapEntry<K, V> next) {
    LinkedEntry<K, V> header = this.header;
    LinkedEntry<K, V> oldTail = header.prv;
    LinkedEntry<K, V> newTail
            = new LinkedEntry<K,V>(key, value, hash, next, header, oldTail);
    return oldTail.nxt = header.prv = newTail;
}

功能和上面的兩個方法差不多,只沒有判斷是夠進行移除操作

根據給定的key返回指定值

@Override public V get(Object key) {
    /*
     * This method is overridden to eliminate the need for a polymorphic
     * invocation in superclass at the expense of code duplication.
     */
    if (key == null) {
        HashMapEntry<K, V> e = entryForNullKey;
        if (e == null)
            return null;
        if (accessOrder)
            makeTail((LinkedEntry<K, V>) e);
        return e.value;
    }

    int hash = Collections.secondaryHash(key);
    HashMapEntry<K, V>[] tab = table;
    for (HashMapEntry<K, V> e = tab[hash & (tab.length - 1)];
            e != null; e = e.next) {
        K eKey = e.key;
        if (eKey == key || (e.hash == hash && key.equals(eKey))) {
            if (accessOrder)
                makeTail((LinkedEntry<K, V>) e);
            return e.value;
        }
    }
    return null;
}

private void makeTail(LinkedEntry<K, V> e) {
    // Unlink e
    e.prv.nxt = e.nxt;
    e.nxt.prv = e.prv;

    // Relink e as tail
    LinkedEntry<K, V> header = this.header;
    LinkedEntry<K, V> oldTail = header.prv;
    e.nxt = header;
    e.prv = oldTail;
    oldTail.nxt = header.prv = e;
    modCount++;
}

這部分代碼和父類HashMap有些重複,但是優點是減少了由於多態導致的父類方法調用。代碼中的entryForNullKey是父類HahMap中的成員變量,如果沒有NULL key的映射,那麼entryForNullKey也是NULL。

代碼中使用的makeTail()的作用是將給定的映射實體從雙向鏈表中取出,然後添加到鏈表尾部,在訪問操作或者刪除操作過程中,如果accessOrder爲true,也就是需要按訪問順序進行排序的時候,都要調用MakeTail方法,將指定節點移動到尾部。

get()方法的後半部分看起來可能有一些奇怪,不是說LinkedHashMap是基於雙向循環鏈表嗎,爲什麼還有數組的迭代操作??實際上LinkedHashMap是數組和雙向鏈表的結合,HashMap基於數組,LinkedHashMap是其子類,在HashMap的基礎上增加了兩個指針,除了將映射實體加入到數組中之外,還額外維護了兩個指針。這樣理解的話,get()方法的後半部分就比較好理解了,直接是按key的Hash值對數組進行遍歷,如果找到對應的映射,則返回值,否則返回Null

刪除指定節點

@Override void postRemove(HashMapEntry<K, V> e) {
    LinkedEntry<K, V> le = (LinkedEntry<K, V>) e;
    le.prv.nxt = le.nxt;
    le.nxt.prv = le.prv;
    le.nxt = le.prv = null; // Help the GC (for performance)
}

判斷是否包含

@Override public boolean containsValue(Object value) {
    if (value == null) {
        for (LinkedEntry<K, V> header = this.header, e = header.nxt;
                e != header; e = e.nxt) {
            if (e.value == null) {
                return true;
            }
        }
        return false;
    }

    // value is non-null
    for (LinkedEntry<K, V> header = this.header, e = header.nxt;
            e != header; e = e.nxt) {
        if (value.equals(e.value)) {
            return true;
        }
    }
    return false;
}

由於LinkedHashMap繼承於HashMap,支持null key 和null value,所以需要判斷key是否爲空。在父類HashMap中已經有了containsValue()方法了,這裏的重載是基於鏈表的查找,這樣可以減少迭代的開銷。

清空集合

public void clear() {
    super.clear();

    // Clear all links to help GC
    LinkedEntry<K, V> header = this.header;
    for (LinkedEntry<K, V> e = header.nxt; e != header; ) {
        LinkedEntry<K, V> nxt = e.nxt;
        e.nxt = e.prv = null;
        e = nxt;
    }

    header.nxt = header.prv = header;
}

清空操作不僅僅是清空鏈表間的指針鏈,還需要調用父類的clear()清空映射實體

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