Java集合源碼分析之Map(六):LinkedHashMap_一點課堂(多岸學院)

LinkedHashMapHashMap的子類,因此也具有HashMap的諸多特性。不一樣的是,LinkedHashMap還維護了一個雙向鏈表,以保證經過Iterator遍歷時順序與插入順序一致。除此以外,它還支持Access Order,即按照元素被訪問的順序來排序,咱們熟知的LRUCache底層就依賴於此。如下是文檔中須要咱們注意的點:java

Hash table and linked list implementation of the Map interface, with predictable iteration order. This implementation differs from HashMap in that it maintains a doubly-linked list running through all of its entries. This linked list defines the iteration ordering, which is normally the order in which keys were inserted into the map (insertion-order). Note that insertion order is not affected if a key is re-inserted into the map.node

A special LinkedHashMap(int,float,boolean) constructor is provided to create a linked hash map whose order of iteration is the order in which its entries were last accessed, from least-recently accessed to most-recently (access-order). This kind of map is well-suited to building LRU caches.數組

The removeEldestEntry(Map.Entry) method may be overridden to impose a policy for removing stale mappings automatically when new mappings are added to the map.app

Note, however, that the penalty for choosing an excessively high value for initial capacity is less severe for this class than for HashMap, as iteration times for this class are unaffected by capacity.less

下面咱們就從構造函數和成員變量開始分析其具體實現。ide

構造函數與成員變量

成員變量

在分析成員變量前,咱們先看下其存儲元素的結構。函數

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

這個EntryHashMap中被引用過,主要是爲了能讓LinkedHashMap也支持樹化。在這裏則是用來存儲元素。學習

// 雙向鏈表的頭,用做AccessOrder時也是最老的元素
transient LinkedHashMap.Entry<K,V> head;

// 雙向鏈表的尾,用做AccessOrder時也是最新的元素
transient LinkedHashMap.Entry<K,V> tail;

// true則爲訪問順序,false則爲插入順序
final boolean accessOrder;

構造函數

關於LinkedHashMap的構造函數咱們只關注一個,其餘的都和HashMap相似,只是把accessOrder設置爲了false。在上邊的文檔說過,initialCapacity並無在HashMap中那般重要,由於鏈表不須要像數組那樣必須先聲明足夠的空間。下面這個構造函數是支持訪問順序的。ui

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

重要方法

LinkedHashMap並無再實現一整套增刪改查的方法,而是經過複寫HashMap在此過程當中定義的幾個方法來實現的。對此不熟悉的能夠查看文末關於HashMap分析的文章,或者對照HashMap的源碼來看。this

插入一個元素

HashMap在插入時,調用了newNode來新建一個節點,或者是經過replacementNode來替換值。在樹化時也有兩個對應的方法,分別是newTreeNodereplacementTreeNode。完成以後,還調用了afterNodeInsertion方法,這個方法容許咱們在插入完成後作些事情,默認是空實現。

爲了方便分析,咱們會對比HashMap中的實現與LinkedHashMap的實現,來摸清它是如何作的。

// HashMap中的實現
Node<K, V> newNode(int hash, K key, V value, Node<K, V> next) {
    return new Node<>(hash, key, value, next);
}

// LinkedHashMap中的實現
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<K, V> replacementNode(Node<K, V> p, Node<K, V> next) {
    return new Node<>(p.hash, p.key, p.value, next);
}

// LinkedHashMap中的實現
Node<K,V> replacementNode(Node<K,V> p, Node<K,V> next) {
    LinkedHashMap.Entry<K,V> q = (LinkedHashMap.Entry<K,V>)p;
    LinkedHashMap.Entry<K,V> t =
        new LinkedHashMap.Entry<K,V>(q.hash, q.key, q.value, next);
    transferLinks(q, t);
    return t;
}

// newTreeNode和replacementTreeNode和此相似

經過以上對比,能夠發現,LinkedHashMap在新增時,調用了linkNodeLast,再替換時調用了transferLinks。如下是這兩個方法的實現。

// 就是將元素掛在鏈尾
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;
    }
}

// 用dst替換src
private void transferLinks(LinkedHashMap.Entry<K,V> src,
                            LinkedHashMap.Entry<K,V> dst) {  
    LinkedHashMap.Entry<K,V> b = dst.before = src.before;
    LinkedHashMap.Entry<K,V> a = dst.after = src.after;
    if (b == null)
        head = dst;
    else
        b.after = dst;
    if (a == null)
        tail = dst;
    else
        a.before = dst;
}

最後咱們看下afterNodeInsertion作了哪些事情吧:

// evict在HashMap中說過,爲false表示是建立階段
void afterNodeInsertion(boolean evict) { // possibly remove eldest
    LinkedHashMap.Entry<K,V> first;
    // 不是建立階段
    if (evict && (first = head) != null && removeEldestEntry(first)) {
        K key = first.key;
        // 自動刪除最老的元素,也就是head元素
        removeNode(hash(key), key, null, false, true);
    }
}

removeEldestEntry是當想要在插入元素時自動刪除最老的元素時須要複寫的方法。其默認實現以下:

protected boolean removeEldestEntry(Map.Entry<K,V> eldest) {
    return false;
}

查詢

由於要支持訪問順序,因此獲取元素的方法和HashMap也有所不一樣。下面咱們看下其實現:

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

getNode方法是在HashMap中實現的,因此這是包裝了一下HashMap的方法,並添加了一個afterNodeAccess,其實現以下:

void afterNodeAccess(Node<K,V> e) { // move node to last
    LinkedHashMap.Entry<K,V> last;
    // e元素不在末尾
    if (accessOrder && (last = tail) != e) {
        // p是e,b是前一個元素,a是後一個元素
        LinkedHashMap.Entry<K,V> p =
            (LinkedHashMap.Entry<K,V>)e, b = p.before, a = p.after;
        // e要放在末尾,因此沒有after
        p.after = null;

        // 把e去掉,把b和a接起來
        if (b == null)
            head = a;
        else
            b.after = a;
        if (a != null)
            a.before = b;
        else
            last = b;

        //把e接在末尾
        if (last == null)
            head = p;
        else {
            p.before = last;
            last.after = p;
        }
        tail = p;
        ++modCount;
    }
}

關於LinkedHashMap的分析就到這裏了,其餘關於Iterator的內容都和Collection是大同小異的,感興趣的能夠去查看相關源碼。


【感謝您能看完,若是可以幫到您,麻煩點個贊~】

更多經驗技術歡迎前來共同窗習交流: 一點課堂-爲夢想而奮鬥的在線學習平臺 http://www.yidiankt.com/

![關注公衆號,回覆「1」免費領取-【java核心知識點】] file

QQ討論羣:616683098

QQ:3184402434

想要深刻學習的同窗們能夠加我QQ一塊兒學習討論~還有全套資源分享,經驗探討,等你哦! 在這裏插入圖片描述

相關文章
相關標籤/搜索