Java LinkedHashMap類源碼解析

LinkedHashMap繼承了HashMap,他在HashMap的基礎上增長了一個雙向鏈表的結構,鏈表默認維持key插入的順序,重複的key值插入不會改變順序,適用於使用者須要返回一個順序相同的map對象的狀況。還能夠生成access-order順序的版本,按照最近訪問順序來存儲,剛被訪問的結點處於鏈表的末尾,適合LRU,put get compute merge都算做一次訪問,其中put key值相同的結點也算做一次訪問,replace只有在換掉一個鍵值對的時候纔算一次訪問,putAll產生的訪問順序取決於本來map的迭代器實現。安全

在插入鍵值對時,能夠經過對removeEldestEntry重寫來實現新鍵值對插入時自動刪除最舊的鍵值對函數

擁有HashMap提供的方法,迭代器由於是經過遍歷雙向鏈表,因此額外開銷與size成正比與capacity無關,所以選擇過大的初始大小對於遍歷時間的增長沒有HashMap嚴重,後者的遍歷時間依賴與capacity。spa

一樣是非線程安全方法,對於LinkedHashMap來講,修改結構的操做除了增長和刪除鍵值對外,還有對於access-order時進行了access致使迭代器順序改變,主要是get操做,對於插入順序的來講,僅僅修改一個已有key值的value值不是一個修改結構的操做,但對於訪問順序,put和get已有的key值會改變順序。迭代器也是fail-fast設計,可是fail-fast只是一個調試功能,一個設計良好的程序不該該出現這個錯誤線程

由於HashMap加入了TreeNode,因此如今LinkedHashMap也有這個功能設計

 如下描述中的鏈表,若無特別說明都是指LinkedHashMap的雙向鏈表指針


 

先來看一下基本結構,每一個鍵值對加入了先後指針,集合加入了頭尾指針來造成雙向鏈表,accessOrder表明鏈表是以訪問順序仍是插入順序存儲調試

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

    /**
     * The head (eldest) of the doubly linked list.頭部
     */
    transient LinkedHashMap.Entry<K,V> head;

    /**
     * The tail (youngest) of the doubly linked list.尾部
     */
    transient LinkedHashMap.Entry<K,V> tail;

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

而後是幾個內部方法。linkNodeLast將p鏈接到鏈表尾部code

    private void linkNodeLast(LinkedHashMap.Entry<K,V> p) {
        LinkedHashMap.Entry<K,V> last = tail;
        tail = p;
        if (last == null)
            head = p;//本來鏈表爲空則p同時爲頭部
        else {
            p.before = last;
            last.after = p;
        }
    }

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

reinitialize在調用HashMap方法的基礎上,將head和tail設爲nullblog

    void reinitialize() {
        super.reinitialize();
        head = tail = null;
    }

newNode生成一個LinkedHashMap結點,next指向e,插入到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);//新建一個鍵值對,next指向e
        linkNodeLast(p);//p插入到LinkedHashMap鏈表末端
        return p;
    }

replacementNode根據原結點生成一個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);//生成一個新的鍵值對next是給出的next參數
        transferLinks(q, t);//用t替換q
        return t;
    }

newTreeNode生成一個TreeNode結點,next指向next,插入到LinkedHashMap鏈表末端

    TreeNode<K,V> newTreeNode(int hash, K key, V value, Node<K,V> next) {
        TreeNode<K,V> p = new TreeNode<K,V>(hash, key, value, next);//生成一個TreeNode,next指向參數next
        linkNodeLast(p);//p插入到LinkedHashMap鏈表末端
        return p;
    }

replacementTreeNode根據結點p生成一個新的TreeNode,next設爲給定的next,替換本來的p

    TreeNode<K,V> replacementTreeNode(Node<K,V> p, Node<K,V> next) {
        LinkedHashMap.Entry<K,V> q = (LinkedHashMap.Entry<K,V>)p;
        TreeNode<K,V> t = new TreeNode<K,V>(q.hash, q.key, q.value, next);
        transferLinks(q, t);//根據結點p生成一個新的TreeNode,next設爲給定的next,替換本來的p
        return t;
    }

afterNodeRemoval從LinkedHashMap的鏈上移除結點e

    void afterNodeRemoval(Node<K,V> e) { 
        LinkedHashMap.Entry<K,V> p =
            (LinkedHashMap.Entry<K,V>)e, b = p.before, a = p.after;
        p.before = p.after = null;
        if (b == null)
            head = a;
        else
            b.after = a;
        if (a == null)
            tail = b;
        else
            a.before = b;
    }

afterNodeInsertion可能移除最舊的結點,須要evict爲true同時鏈表不爲空同時removeEldestEntry須要重寫

    void afterNodeInsertion(boolean evict) { 
        LinkedHashMap.Entry<K,V> first;
        if (evict && (first = head) != null && removeEldestEntry(first)) {//removeEldestEntry須要重寫才從發揮做用,不然必定返回false
            K key = first.key;//移除鏈表頭部的結點
            removeNode(hash(key), key, null, false, true);
        }
    }

afterNodeAccess在訪問事後將結點e移動到鏈表尾部,須要Map是access-order,若移動成功則增長modCount

    void afterNodeAccess(Node<K,V> e) { 
        LinkedHashMap.Entry<K,V> last;
        if (accessOrder && (last = tail) != e) {//Map是access-order同時e不是鏈表的尾部
            LinkedHashMap.Entry<K,V> p =
                (LinkedHashMap.Entry<K,V>)e, b = p.before, a = p.after;
            p.after = null;
            if (b == null)//將結點e從鏈表中剪下
                head = a;
            else
                b.after = a;
            if (a != null)
                a.before = b;
            else
                last = b;
            if (last == null)
                head = p;
            else {
                p.before = last;
                last.after = p;
            }
            tail = p;//結點e移動到鏈表尾部
            ++modCount;//由於有access-order下結點被移動,因此增長modCount
        }
    }

構造函數方面,accessOrder默認是false插入順序,初始大小爲16,負載因子爲0.75,這裏是同HashMap。複製構造也是調用了HashMap.putMapEntries方法

containsValue遍歷鏈表尋找相等的value值,這個操做必定不會形成結構改變

    public boolean containsValue(Object value) {
        for (LinkedHashMap.Entry<K,V> e = head; e != null; e = e.after) {//檢查一樣是根據LinkedHashMap提供的鏈表順序進行遍歷
            V v = e.value;
            if (v == value || (value != null && value.equals(v)))
                return true;
        }
        return false;
    }

 get方法複用HashMap的getNode方法,若找到結點且Map是訪問順序時,要將訪問的結點放到鏈表最後,若沒找到則返回null。而getOrDefault僅有的區別是沒找到時返回defaultValue

    public V get(Object key) {
        Node<K,V> e;
        if ((e = getNode(hash(key), key)) == null)//複用HashMap的getNode方法
            return null;
        if (accessOrder)
            afterNodeAccess(e);//access-order時將e放到隊尾
        return e.value;
    }

    public V getOrDefault(Object key, V defaultValue) {
       Node<K,V> e;
       if ((e = getNode(hash(key), key)) == null)
           return defaultValue;//複用HashMap的getNode方法,若沒有找到對應的結點則返回defaultValue
       if (accessOrder)
           afterNodeAccess(e);//access-order時將e放到隊尾
       return e.value;
   }

clear方法在HashMap的基礎上要把head和tail設爲null

    public void clear() {
        super.clear();
        head = tail = null;
    }

removeEldestEntry在put和putAll插入鍵值對時調用,本來是必定返回false的,若是要自動刪除最舊的鍵值對要返回true,須要進行重寫。好比下面這個例子,控制size不能超過100

    private static final int MAX_ENTRIES = 100;

     protected boolean removeEldestEntry(Map.Entry eldest) {
        return size() &gt; MAX_ENTRIES;
     }

下面兩個方法和HashMap類似,返回key的Set和value的Collection還有返回鍵值對的Set,這個是直接引用,因此對它們的remove之類的修改會直接反饋到LinkedHashMap上

    public Set<K> keySet() {
        Set<K> ks = keySet;
        if (ks == null) {
            ks = new LinkedKeySet();
            keySet = ks;
        }
        return ks;//返回key值的set
    }

    public Collection<V> values() {
        Collection<V> vs = values;
        if (vs == null) {
            vs = new LinkedValues();
            values = vs;
        }
        return vs;//返回一個包含全部value值的Collection
    }

    public Set<Map.Entry<K,V>> entrySet() {
        Set<Map.Entry<K,V>> es;
        return (es = entrySet) == null ? (entrySet = new LinkedEntrySet()) : es;//返回一個含有全部鍵值對的Set
    }

檢查HashMap的putVal方法,咱們能夠看到在找到了相同key值並修改value值時會調用afterNodeAccess,對於access-order會改變結點順序

            if (e != null) { // 找到了相同的key則修改value值並返回舊的value
                V oldValue = e.value;
                if (!onlyIfAbsent || oldValue == null)
                    e.value = value;
                afterNodeAccess(e);
                return oldValue;
            }
相關文章
相關標籤/搜索