Java容器類框架分析(4)LinkedHashMap源碼分析

概述

在分析LinkedHashMap的源碼以前先看一下LinkedHashMap的繼承關係算法

LinkedHashMap繼承關係
LinkedHashMap繼承關係

經過上述繼承關係能夠看到,LinkedHashMap繼承自HashMap,也就是具備HashMap的全部功能,而後再看看源碼中的註釋:數組

  • 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. Note that insertion order is not affected if a key is re-inserted into the map.
  • 一個實現了Map接口,而且能夠知道迭代順序的哈希表個鏈表。這個實現跟HashMap的區別在於它維持了一個鏈接全部Entry的鏈表。這個鏈表定義了迭代的順序,默認的迭代順序就是key被插入的順序。注意若是一個key被從新插入是不會影響鏈表的插入順序的。

從註釋中能夠了解到,LinkedHashMap本身維護了一個雙向鏈表,確切地說是一個雙向循環鏈表,下面看一下雙向循環鏈表安全

  • 雙向循環鏈表的結構示意圖

雙向循環鏈表
雙向循環鏈表

  • 雙向循環鏈表的元素插入

雙向循環鏈表表插入元素
雙向循環鏈表表插入元素

正文

成員變量

private static final long serialVersionUID = 3801124242820219131L;
    //雙鏈表的頭結點
    private transient LinkedHashMapEntry<K,V> header;
    //雙鏈表的迭代順序,true表示按照訪問的順序,false表示插入的順序
    private final boolean accessOrder;複製代碼

LinkedHashMapEntry

private static class LinkedHashMapEntry<K,V> extends HashMapEntry<K,V> {
        // before爲指向上一個節點的指針,after爲指向下一個節點的指針
        LinkedHashMapEntry<K,V> before, after;
        //構造方法
        LinkedHashMapEntry(int hash, K key, V value, HashMapEntry<K,V> next) {
            super(hash, key, value, next);
        }

      //刪除某個節點,也就是將當前節點的前一個節點跟後一個節點鏈接在一塊兒
        private void remove() {
        //將上一個節點的尾指針指向下個節點
            before.after = after;
        //將下一個節點的頭指針指向上一個節點
            after.before = before;
        }

       //在某個節點以前插入一個節點,結合上面你的示意圖比較好理解
        private void addBefore(LinkedHashMapEntry<K,V> existingEntry) {
            //將當前節點的after指針指向插圖的節點
            after  = existingEntry;
            //將當前節點的before指針指向existingEntry的上一個節點
            before = existingEntry.before;
            //before的尾節點指向當前節點
            before.after = this;
            //after的頭結點指向當前節點
            after.before = this;
        }

         //當一個已經存在的entry被get方法調用或者被set方法修改的時候此方法被父類(HashMap)調用,若是access-ordered爲true,那麼entry就會被移動到鏈表的尾部,不然不做處理。
        void recordAccess(HashMap<K,V> m) {
        //將HashMap強轉成LinkedHashMap
            LinkedHashMap<K,V> lm = (LinkedHashMap<K,V>)m;
            if (lm.accessOrder) {
               //若是accessOrder爲true
                lm.modCount++;
                //先移除此entry
                remove();
                //將lm添加到隊尾
                addBefore(lm.header);
            }
        }

        void recordRemoval(HashMap<K,V> m) {
            remove();
        }
    }複製代碼

構造方法

LinkedHashMap的構造方法其實就是間接調用了HashMap的構造方法,而後給迭代標誌位accessOrder一個false,也就是默認按照插入的順序進行排序,比較簡單bash

空的構造方法

public LinkedHashMap() {
        super();
        accessOrder = false;
    }複製代碼

傳入一個容量

public LinkedHashMap(int initialCapacity) {
        super(initialCapacity);
        accessOrder = false;
    }複製代碼

傳入一個容量跟負載因子

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

傳入一個Map

public LinkedHashMap(Map<? extends K, ? extends V> m) {
        super(m);
        accessOrder = false;
    }複製代碼

get方法

public V get(Object key) {
        LinkedHashMapEntry<K,V> e = (LinkedHashMapEntry<K,V>)getEntry(key);
        if (e == null)
            return null;
        //調用了get方法,就會將當前的entry放到鏈表的尾部,刪除的時候就會最後刪除
        e.recordAccess(this);
        return e.value;
    }複製代碼

put方法

經過查找發現LinkedHashMap並無複寫Get方法,只是複寫了addEntry多線程

void addEntry(int hash, K key, V value, int bucketIndex) {
    //這個是Android作的一些修改,略微不一樣於Java的SDK
        LinkedHashMapEntry<K,V> eldest = header.after;
        if (eldest != header) {
            boolean removeEldest;
            size++;
            try {
            //判斷是否移除最近最少使用的Entry
            removeEldest = removeEldestEntry(eldest);
            } finally {
                size--;
            }
            if (removeEldest) {
            //根據返回值是否移除最近最少使用的entry
               removeEntryForKey(eldest.key);
            }
        }
    //調用父類的addEntry
        super.addEntry(hash, key, value, bucketIndex);
    }複製代碼

//removeEldestEntry的返回值老是false,這個須要根據實際狀況來複寫,默認不移除併發

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

containsValue

HashMap

public boolean containsValue(Object value) {
        if (value == null)
            return containsNullValue();

        HashMapEntry[] tab = table;
        for (int i = 0; i < tab.length ; i++)
            for (HashMapEntry e = tab[i] ; e != null ; e = e.next)
                if (value.equals(e.value))
                    return true;
        return false;
    }

     private boolean containsNullValue() {
        HashMapEntry[] tab = table;
        for (int i = 0; i < tab.length ; i++)
            for (HashMapEntry e = tab[i] ; e != null ; e = e.next)
                if (e.value == null)
                    return true;
        return false;
    }複製代碼

LinkedHashMap

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

仔細觀察一下發現,LinkedHashMap用雙鏈表自身的特性去遍歷了整個鏈表,從而替換了HashMap中的循環遍歷,這個是由於鏈表遍歷的效率更高。ide

  • HashMap底層是Entry數組,因此循環遍歷效率高,經過下標能夠直接拿到對應的元素
  • LinkedHashMap底層是鏈表,沒法經過下標拿到對應的元素,因此只能挨個遍歷,效率較低,因此經過指針遍歷效率會高一些

transfer方法:擴容

HashMap

void transfer(HashMapEntry[] newTable) {
        int newCapacity = newTable.length;
        for (HashMapEntry<K, V> e : table) {
            while (null != e) {
                HashMapEntry<K, V> next = e.next;
                int i = indexFor(e.hash, newCapacity);
                e.next = newTable[i];
                newTable[i] = e;
                e = next;
            }
        }
    }複製代碼

LinkedHashMap

@Override
    void transfer(HashMapEntry[] newTable) {
        int newCapacity = newTable.length;
        for (LinkedHashMapEntry<K,V> e = header.after; e != header; e = e.after) {
            int index = indexFor(e.hash, newCapacity);
            e.next = newTable[index];
            newTable[index] = e;
        }
    }複製代碼

其實跟containsValue同樣,用自身的鏈表特性進行迭代,提升效率ui

createEntry

插入一個元素,默認是插入到隊列的尾部this

void createEntry(int hash, K key, V value, int bucketIndex) {
        HashMapEntry<K,V> old = table[bucketIndex];
        LinkedHashMapEntry<K,V> e = new LinkedHashMapEntry<>(hash, key, value, old);
        table[bucketIndex] = e;
        e.addBefore(header);
        size++;
    }複製代碼

迭代

LinkedHashMap徹底重寫了HashMap的迭代器spa

LinkedHashIterator

private abstract class LinkedHashIterator<T> implements Iterator<T> {
        //默認的下個entry指的是鏈表的第1個entry節點
        LinkedHashMapEntry<K,V> nextEntry  = header.after;
        //上一次返回的entry,也就是當前節點
        LinkedHashMapEntry<K,V> lastReturned = null;
        //用來判斷是不是多線程併發
        int expectedModCount = modCount;
        //是否還有下一個節點
        public boolean hasNext() {
            return nextEntry != header;
        }
        //移除掉某個entry節點
        public void remove() {
            if (lastReturned == null)
                throw new IllegalStateException();
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
            //移除當前entry節點
            LinkedHashMap.this.remove(lastReturned.key);
            lastReturned = null;
            expectedModCount = modCount;
        }

        Entry<K,V> nextEntry() {
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
            if (nextEntry == header)
                throw new NoSuchElementException();
            LinkedHashMapEntry<K,V> e = lastReturned = nextEntry;
            //返回當前節點的下一個節點
            nextEntry = e.after;
            return e;
        }
    }複製代碼

KeyIterator

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

ValueIterator

private class ValueIterator extends LinkedHashIterator<V> {
        public V next() { return nextEntry().getValue(); }
    }複製代碼

EntryIterator

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

Key,Value,Entry分別複寫了next方法

到此爲止,基本上已經分析完成了LinkedHashedMap源碼的基本分析,主要仍是須要對雙向循環鏈表比較熟悉,它自己實際上也只是循環鏈表的一個實現類

總結

  1. LinkedHashMap繼承自HashMap,非線程安全
  2. LinkedHashMap本身維護了一個雙向循環鏈表,有一個head指針,經過將最近最少使用的元素放到隊列的頭部,新插入的以及常常使用的元素放到尾部來幫助實現LRU算法
  3. LinkedHashMap有一個排序的標誌位accessOrder,默認爲false,即按照插入的順序進行排序,若是爲true,就將該元素放到隊列尾部
  4. 雖然LinkedHashMap並無實現put方法,可是覆蓋了addEntry方法,實際上並無什麼用
  5. LRU算法的實現也是利用了LinkedHashMap的accessOrder標誌位,同時也必須複寫removeEldestEntry方法,根據實際需求決定是否刪除最近最少使用的元素。
相關文章
相關標籤/搜索