java容器源碼分析(七)——LinkedHashMap

本文內容:java

LinkedHashMap概述數組

LinkedHashMap源碼分析緩存


LinkedHashMap概述性能優化


LinkedHashMap相似 於HashMap,區別在於採用迭代器迭代每一個元素時,其順序是按照插入次序或者是LRU次序。
ide

其繼承 關係以下:函數

LinkedHashMap直接繼承了HashMap,實現了Map接口。源碼分析

LinkedHashMap用Hash存儲全部元素,但迭代時卻又能夠按順序遍歷,這是如何作到的呢?性能

LinkedHashMap源碼分析優化


看一下LinkedHashMap的屬性this

/**
 * The head of the doubly linked list.
 */
private transient Entry<K,V> header;

/**
 * The iteration ordering method for this linked hash map: <tt>true</tt>
 * for access-order, <tt>false</tt> for insertion-order.
 *
 * @serial
 */
private final boolean accessOrder;

其中header列表記錄着元素的插入或者LRU次序,因此很明顯,LinkedHashMap可以迭代器順序訪問必定和這個header有關。其實確實是的,header是一個循環雙向鏈表!accessOrder則用來標記是按插入順序仍是訪問順序。

構造函數:

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

調用了HashMap的構造函數,在HashMap的構造函數中調用了init()方法

@Override
void init() {
    header = new Entry<>(-1, null, null, null);
    header.before = header.after = header;
}

init是初始化header鏈表。header鏈表是Entry類型,LinkedHashMap的一個內部靜態類,繼承了HashMap.Entry。

private static class Entry<K,V> extends HashMap.Entry<K,V> {
    // These fields comprise the doubly linked list used for iteration.
    Entry<K,V> before, after;

    Entry(int hash, K key, V value, HashMap.Entry<K,V> next) {
        super(hash, key, value, next);
    }

   //省略
}


看LinkedHashMap,大部分都是重寫(override)父類的一些方法。

/**
 * This override differs from addEntry in that it doesn't resize the
 * table or remove the eldest entry.
 */
void createEntry(int hash, K key, V value, int bucketIndex) {
    HashMap.Entry<K,V> old = table[bucketIndex];
    Entry<K,V> e = new Entry<>(hash, key, value, old);
    table[bucketIndex] = e;
    e.addBefore(header);
    size++;
}

createEntry不只把元素添加進了table數組,還經過addBefore將其添加進header鏈表。

private void addBefore(Entry<K,V> existingEntry) {
    after  = existingEntry;
    before = existingEntry.before;
    before.after = this;
    after.before = this;
}

在header前面插入該元素。最後造成這樣的鏈表:

這個鏈表在迭代器中用。

void addEntry(int hash, K key, V value, int bucketIndex) {
    super.addEntry(hash, key, value, bucketIndex);

    // Remove eldest entry if instructed
    Entry<K,V> eldest = header.after;
    if (removeEldestEntry(eldest)) {
        removeEntryForKey(eldest.key);
    }
}
protected boolean removeEldestEntry(Map.Entry<K,V> eldest) {
    return false;
}

這裏,會調用removeEntryForKey去除最老的元素,但removeEldestEntry方法返回的是false,這樣不是永遠不會執行removeEntryForKey嗎?是的。

這樣作是爲了讓LinkedHashMap的行爲是符合正常的Map,不會由於增長元素而把舊的元素給去除。

那直接忽略不就好了嗎?

看註釋,若是咱們想實現一個cache,只須要重載LinkedHashMap,而後重寫removeEldestEntry就能夠完成簡單的,限制大小的LRU緩存了。好比實現一個100元素的cache,能夠這樣寫:

  private static final int MAX_ENTRIES = 100;
      
  protected boolean removeEldestEntry(Map.Entry eldest) {
      return size() > MAX_ENTRIES;
  }

再看一下transfer

@Override
void transfer(HashMap.Entry[] newTable, boolean rehash) {
    int newCapacity = newTable.length;
    for (Entry<K,V> e = header.after; e != header; e = e.after) {
        if (rehash)
            e.hash = (e.key == null) ? 0 : hash(e.key);
        int index = indexFor(e.hash, newCapacity);
        e.next = newTable[index];
        newTable[index] = e;
    }
}

這個方法被父類的resize調用,重寫它是爲了實現性能優化!爲何呢?由於這裏直接用了header鏈表,而HashMap是遍歷全部的table槽。

public boolean containsValue(Object value) {
    // Overridden to take advantage of faster iterator
    if (value==null) {
        for (Entry e = header.after; e != header; e = e.after)
            if (e.value==null)
                return true;
    } else {
        for (Entry e = header.after; e != header; e = e.after)
            if (value.equals(e.value))
                return true;
    }
    return false;
}

遍歷header鏈表,查看是否包含該value.

public V get(Object key) {
    Entry<K,V> e = (Entry<K,V>)getEntry(key);
    if (e == null)
        return null;
    e.recordAccess(this);
    return e.value;
}

和HashMap方法相比,多了recordAccess方法的調用。recordAccess方法在Entry中實現。

void recordAccess(HashMap<K,V> m) {
    LinkedHashMap<K,V> lm = (LinkedHashMap<K,V>)m;
    if (lm.accessOrder) {
        lm.modCount++;
        remove();
        addBefore(lm.header);
    }
}
private void remove() {
    before.after = after;
    after.before = before;
}

由代碼可知,若是accessOrder被設置成true,就能夠有LRU的功能。

最後還剩下iterator方法沒看,咱們說LinkedHashMap是支持按插入順序或者LRU順序的,說的是遍歷的時候。它的實現方法就是遍歷header鏈表。

把LinkedHashMap的迭代器所有貼出來,和HashMap差很少:

private class KeyIterator extends LinkedHashIterator<K> {
    public K next() { return nextEntry().getKey(); }
}

private class ValueIterator extends LinkedHashIterator<V> {
    public V next() { return nextEntry().value; }
}

private class EntryIterator extends LinkedHashIterator<Map.Entry<K,V>> {
    public Map.Entry<K,V> next() { return nextEntry(); }
}

// These Overrides alter the behavior of superclass view iterator() methods
Iterator<K> newKeyIterator()   { return new KeyIterator();   }
Iterator<V> newValueIterator() { return new ValueIterator(); }
Iterator<Map.Entry<K,V>> newEntryIterator() { return new EntryIterator(); }

它們都繼承了LinkedHashIterator,LinkedHashIterator是LinkedHashMap的內部抽象類,繼承Iterator

private abstract class LinkedHashIterator<T> implements Iterator<T>

LinkedHashIterator的屬性以下:

Entry<K,V> nextEntry    = header.after;
Entry<K,V> lastReturned = null;
int expectedModCount = modCount;

hasNext方法:

public boolean hasNext() {
    return nextEntry != header;
}

remove方法

public void remove() {
    if (lastReturned == null)
        throw new IllegalStateException();
    if (modCount != expectedModCount)
        throw new ConcurrentModificationException();

    LinkedHashMap.this.remove(lastReturned.key);
    lastReturned = null;
    expectedModCount = modCount;
}

nextEntry方法

Entry<K,V> nextEntry() {
    if (modCount != expectedModCount)
        throw new ConcurrentModificationException();
    if (nextEntry == header)
        throw new NoSuchElementException();

    Entry<K,V> e = lastReturned = nextEntry;
    nextEntry = e.after;
    return e;
}

iterator的實現思路和HashMap很類似,代碼找着header鏈表的結構就很容易理解了。

總結


LinkedHashMap使用hash來存放元素,同時也將該元素存放在header鏈表中,這樣,就能夠hash方式get/put元素,List方式迭代元素。

相關文章
相關標籤/搜索