8,HashMap子類-LinkedHashMap

在上一篇隨筆中,分析了HashMap的源碼,裏面涉及到了3個鉤子函數(afterNodeAccess(e),afterNodeInsertion(evict),afterNodeRemoval(node)),用來預設給子類——LinkedHashMap調用。 node

一,LinkedHashMap數據結構

能夠從上圖中看到,LinkedHashMap數據結構相比較於HashMap來講,添加了雙向指針,分別指向前一個節點——before和後一個節點——after,從而將全部的節點已鏈表的形式串聯一塊兒來。數據結構爲(數組 + 單鏈表 + 紅黑樹 + 雙鏈表),圖中的標號是結點插入的順序。 數組

二,LinkedHashMap源碼

1,LinkedHashMap結構 數據結構

LinkedHashMap繼承HashMap,因此HashMap中的非private方法和字段,均可以在LinkedHashMap直接中訪問。 函數

public class LinkedHashMap<K,V>  extends HashMap<K,V> implements Map<K,V> {
    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 static final long serialVersionUID = 3801124242820219131L;
    // 鏈表頭結點
    transient LinkedHashMap.Entry<K,V> head;
    // 鏈表尾結點
    transient LinkedHashMap.Entry<K,V> tail;
    /**
     * 用來指定LinkedHashMap的迭代順序,
     * true則表示按照基於訪問的順序來排列,意思就是最近使用的entry,放在鏈表的最末尾
     * false則表示按照插入順序來
     */
    final boolean accessOrder;
}

2,構造函數 this

LinkedHashMap提供了五種方式的構造器,全部構造函數的第一行都會調用父類構造函數,使用super關鍵字,以下 spa

構造器一: 指針

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

accessOrder默認爲false,access爲true表示以後訪問順序按照元素的訪問順序進行,即不按照以前的插入順序了,access爲false表示按照插入順序訪問。 code

構造器二: blog

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

putMapEntries是調用到父類HashMap的函數。

構造器五:

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

經過指定accessOrder的值,從而控制訪問順序。

3,LinkedHashMap.Entry內部類

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

LinkedHashMap.Entry繼承自HashMap.Node,在HashMap.Node基礎上增長了先後兩個指針域。

4,部分函數

4.1,get()函數

public V get(Object key) {
    Node<K,V> e;
    if ((e = getNode(hash(key), key)) == null)
        return null;
    //accessOrder爲true則表示按照基於訪問的順序來排列,意思就是最近使用的entry,放在鏈表的最末尾
    //在取值後對參數accessOrder進行判斷,若是爲true,執行afterNodeAccess
    if (accessOrder)
        afterNodeAccess(e);
    return e.value;
}
//此函數執行的效果就是將最近使用的Node,放在鏈表的最末尾
void afterNodeAccess(Node<K,V> e) {
    LinkedHashMap.Entry<K,V> last;
    //僅當按照LRU原則且e不在最末尾,才執行修改鏈表,將e移到鏈表最末尾的操做
    if (accessOrder && (last = tail) != e) {
        //將e賦值臨時節點p, b是e的前一個節點, a是e的後一個節點
        LinkedHashMap.Entry<K,V> p = (LinkedHashMap.Entry<K,V>)e, b = p.before, a = p.after;
        //設置p的後一個節點爲null,由於執行後p在鏈表末尾,after確定爲null
        p.after = null;
        
        //狀況一(p爲頭部):p前一個節點爲null
        if (b == null)
            head = a;
        else
            b.after = a;
        //狀況二(p爲尾部):p的後一個節點爲null
        if (a != null)
            a.before = b;
        else
            last = b;
        //狀況三(p爲鏈表裏的第一個節點)
        if (last == null)
            head = p;
        else {
            //正常狀況,將p設置爲尾節點的準備工做,p的前一個節點爲原先的last,last的after爲p
            p.before = last;
            last.after = p;
        }
        //將p設置爲尾節點
        tail = p;
        // 修改計數器+1
        ++modCount;
    }
}

概念:

LRU(Least Recently Used): 意思就是最近讀取的數據放在最前面,最先讀取的數據放在最後面,若是這個時候有新的數據進來,那麼最後面存儲的數據淘汰。

說明一下:

正常狀況下:查詢的p在鏈表中間,那麼將p設置到末尾後,它原先的 前節點b 和 後節點a 就變成了先後節點。

狀況一:p爲頭部,前一個節點b不存在,那麼考慮到p要放到最後面,則設置p的後一個節點a爲head。

狀況二:p爲尾部,後一個節點a不存在,那麼考慮到統一操做,設置last爲b。

狀況三:p爲鏈表裏的第一個節點,head=p。

將最近使用的Node,放在鏈表的最末尾示意圖:

4.2,put()方法

LinkedHashMap的put方法調用的仍是HashMap裏的put,不一樣的是重寫了裏面的部分方法。

LinkedHashMap將其中newNode方法以及以前設置下的鉤子方法afterNodeAccess(該方法上面已說明)和afterNodeInsertion進行了重寫,從而實現了加入鏈表的目的。

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;
}
//把新加的節點放在鏈表的最後面。
private void linkNodeLast(LinkedHashMap.Entry<K,V> p) {
    //將tail給臨時變量last
    LinkedHashMap.Entry<K,V> last = tail;
    tail = p;
    //若沒有last,說明p是第一個節點,head=p
    if (last == null)
        head = p;
    else {
        p.before = last;
        last.after = p;
    }
}
//插入後把最老的Entry刪除,不過removeEldestEntry老是返回false,因此不會刪除,估計又是一個鉤子方法給子類用的
void afterNodeInsertion(boolean evict) {
    LinkedHashMap.Entry<K,V> first;
    if (evict && (first = head) != null && removeEldestEntry(first)) {
        K key = first.key;
        removeNode(hash(key), key, null, false, true);
    }
}
protected boolean removeEldestEntry(Map.Entry<K,V> eldest) {
    return false;
}

4.3,remove()方法

在HashMap的remove方法中也有一個鉤子方法afterNodeRemoval。

LinkedHashMap的remove方法調用的仍是HashMap裏的remove,不一樣的是重寫了裏面的部分方法。

void afterNodeRemoval(Node<K,V> e) {
    //記錄e的先後節點b,a
    LinkedHashMap.Entry<K,V> p =
        (LinkedHashMap.Entry<K,V>)e, b = p.before, a = p.after;
    //p已刪除,先後指針都設置爲null,便於GC回收
    p.before = p.after = null;
    if (b == null)
        head = a;
    else
        b.after = a;
    if (a == null)
        tail = b;
    else
        a.before = b;
}

4.4,transferLinks()方法

//替換節點的方法,咱們使用的replacementNode,replacementTreeNode等方法都是經過該方法實現的
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;
}

dst節點替換src節點示意圖:

5,LinkedHashMap的迭代器

abstract class LinkedHashIterator {
    //記錄下一個Entry
    LinkedHashMap.Entry<K,V> next;
    //記錄當前的Entry
    LinkedHashMap.Entry<K,V> current;
    //記錄是否發生了迭代過程當中的修改
    int expectedModCount;
 
    LinkedHashIterator() {
        //初始化的時候把head給next
        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;
    }
 
    public final void remove() {
        Node<K,V> p = current;
        if (p == null)
            throw new IllegalStateException();
        if (modCount != expectedModCount)
            throw new ConcurrentModificationException();
        current = null;
        K key = p.key;
        removeNode(hash(key), key, null, false, false);
        expectedModCount = modCount;
    }
}
相關文章
相關標籤/搜索