Java LinkedHashMap類源碼解析

來源:互聯網
上載者:User

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

在插入索引值對時,可以通過對removeEldestEntry重寫來實現新索引值對插入時自動刪除最舊的索引值對

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

同樣是非安全執行緒方法,對於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串連到鏈表尾部

    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設為null

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

 

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.