天天看點

LinkedHashMap 詳解

本文代碼來自JDK8

性質

  1. LinkedHashMap 繼承于 HashMap, 具備 HashMap 的一切性質;
  2. LinkedHashMap 會按先後插入順序對元素排序周遊;
  3. LinkedHashMap 會額外使用雙向連結清單結構來表示插入的元素.

變量

  1. transient LinkedHashMap.Entry head

    表示雙向連結清單的頭部

  2. transient LinkedHashMap.Entry tail

    表示雙向連結清單的尾部

  3. final boolean accessOrder

    true: 表示把最後通路的節點放到雙向連結清單的最後一位, 通路的方式有替換舊節點和讀取節點

put

LinkedHashMap 的 put 方法也是使用 HashMap 的方法, 不同在于重寫了 newNode(), afterNodeAccess 和 afterNodeInsertion 這幾個方法, 這幾個方法的調用可以看 HashMap-詳解四, 下面具體講講如何重寫這幾個方法.

newNode

/**
 * 根據 key-value 建立雙向連結清單節點
 * e 表示下一個節點, 不過這裡是空值, 不用理會
 */
Node<K,V> newNode(int hash, K key, V value, Node<K,V> e) {
    LinkedHashMap.Entry<K,V> p =
        new LinkedHashMap.Entry<K,V>(hash, key, value, e);
    linkNodeLast(p);
    return p;
}

/**
 * 繼承 HashMap 的靜态内部類 Node
 */
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);
    }
}

/**
 * 把新節點插入到雙向連結清單尾部
 */
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;
    }
}           

afterNodeAccess

/**
 * 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
            last = b;

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

afterNodeInsertion

/**
 * 插入新節點才會觸發該方法
 * 根據 HashMap 的 putVal 方法, evict 一直是 true
 * removeEldestEntry 方法表示移除規則, 在 LinkedHashMap 裡一直傳回 false
 * 是以在 LinkedHashMap 裡這個方法相當于什麼都不做
 */
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 的周遊方式和 HashMap 的一樣, 都是通過 entrySet 方法傳回 Set 執行個體, 然後通過 iterator 方法傳回疊代器進行周遊.

entrySet

/**
 * 傳回 LinkedEntrySet 執行個體, 這是非靜态内部類
 */
public Set<Map.Entry<K,V>> entrySet() {
    Set<Map.Entry<K,V>> es;
    return (es = entrySet) == null ? (entrySet = new LinkedEntrySet()) : es;
}

/**
 * 和 HashMap 的 EntrySet 類一樣繼承 AbstractSet
 * iterator 方法傳回 LinkedEntryIterator 執行個體
 */
final class LinkedEntrySet extends AbstractSet<Map.Entry<K,V>> {
    ...
    public final Iterator<Map.Entry<K,V>> iterator() {
        return new LinkedEntryIterator();
    }
    ...
}

           

next 和 hasNext

/**
 * next 方法實際是調用父類 nextNode 方法傳回節點
 */
final class LinkedEntryIterator extends LinkedHashIterator
        implements Iterator<Map.Entry<K,V>> {
    public final Map.Entry<K,V> next() { return nextNode(); }
}

abstract class LinkedHashIterator {
    LinkedHashMap.Entry<K,V> next;
    LinkedHashMap.Entry<K,V> current;
    int expectedModCount;

    /**
     * 構造函數, 從雙向連結清單頭節點開始周遊
     */
    LinkedHashIterator() {
        next = head;
        expectedModCount = modCount;
        current = null;
    }

    public final boolean hasNext() {
        return next != null;
    }

    /**
     * 周遊比較簡單, 直接讀取下一個節點就行
     */
    final LinkedHashMap.Entry<K,V> nextNode() {
        LinkedHashMap.Entry<K,V> e = next;
        if (modCount != expectedModCount)
            throw new ConcurrentModificationException();
        if (e == null)
            throw new NoSuchElementException();
        current = e;
        next = e.after;
        return e;
    }
    ...
}           

繼續閱讀