【Java集合源碼剖析】LinkedHashmap源碼剖析,linkedhashmap源碼

來源:互聯網
上載者:User

【Java集合源碼剖析】LinkedHashmap源碼剖析,linkedhashmap源碼

轉載請註明出處:http://blog.csdn.net/ns_code/article/details/37867985


    前言:有網友建議分析下LinkedHashMap的源碼,於是花了一晚上時間研究了下,分享出此文(這個系列的最後一篇博文了),希望大家相互學習。LinkedHashMap的源碼理解起來也不難(當然,要建立在對HashMap源碼有較好理解的基礎上)。

    LinkedHashMap簡介

    LinkedHashMap是HashMap的子類,與HashMap有著同樣的儲存結構,但它加入了一個雙向鏈表的頭結點,將所有put到LinkedHashmap的節點一一串成了一個雙向迴圈鏈表,因此它保留了節點插入的順序,可以使節點的輸出順序與輸入順序相同。

    LinkedHashMap可以用來實現LRU演算法(這會在下面的源碼中進行分析)。

    LinkedHashMap同樣是非安全執行緒的,只在單線程環境下使用。

    LinkedHashMap源碼剖析

    LinkedHashMap源碼如下(加入了詳細的注釋):

package java.util;import java.io.*;public class LinkedHashMap<K,V>    extends HashMap<K,V>    implements Map<K,V>{    private static final long serialVersionUID = 3801124242820219131L;//雙向迴圈鏈表的頭結點,整個LinkedHa只喲shMap中只有一個header,//它將雜湊表中所有的Entry貫穿起來,header中不儲存key-value對,只儲存前後節點的引用    private transient Entry<K,V> header;//雙向鏈表中元素定序的標誌位。//accessOrder為false,表示按插入順序排序//accessOrder為true,表示按訪問順序排序    private final boolean accessOrder;//調用HashMap的構造方法來構造底層的數組    public LinkedHashMap(int initialCapacity, float loadFactor) {        super(initialCapacity, loadFactor);        accessOrder = false;//鏈表中的元素預設按照插入順序排序    }//載入因子取預設的0.75f    public LinkedHashMap(int initialCapacity) {super(initialCapacity);        accessOrder = false;    }//載入因子取預設的0.75f,容量取預設的16    public LinkedHashMap() {super();        accessOrder = false;    }//含有子Map的構造方法,同樣調用HashMap的對應的構造方法public LinkedHashMap(Map<? extends K, ? extends V> m) {        super(m);        accessOrder = false;    }//該構造方法可以指定鏈表中的元素排序的規則    public LinkedHashMap(int initialCapacity,float loadFactor,boolean accessOrder) {        super(initialCapacity, loadFactor);        this.accessOrder = accessOrder;    }//覆寫父類的init()方法(HashMap中的init方法為空白),//該方法在父類的構造方法和Clone、readObject中在插入元素前被調用,//初始化一個空的雙向迴圈鏈表,頭結點中不儲存資料,頭結點的下一個節點才開始儲存資料。    void init() {        header = new Entry<K,V>(-1, null, null, null);        header.before = header.after = header;    }//覆寫HashMap中的transfer方法,它在父類的resize方法中被調用,//擴容後,將key-value對重新對應到新的newTable中//覆寫該方法的目的是為了提高複製的效率,//這裡充分利用雙向迴圈鏈表的特點進行迭代,不用對底層的數組進行for迴圈。    void transfer(HashMap.Entry[] newTable) {        int newCapacity = newTable.length;        for (Entry<K,V> e = header.after; e != header; e = e.after) {            int index = indexFor(e.hash, newCapacity);            e.next = newTable[index];            newTable[index] = e;        }    }//覆寫HashMap中的containsValue方法,//覆寫該方法的目的同樣是為了提高查詢的效率,//利用雙向迴圈鏈表的特點進行查詢,少了對數組的外層for迴圈    public boolean containsValue(Object value) {        // Overridden to take advantage of faster iterator        if (value==null) {            for (Entry e = header.after; e != header; e = e.after)                if (e.value==null)                    return true;        } else {            for (Entry e = header.after; e != header; e = e.after)                if (value.equals(e.value))                    return true;        }        return false;    }//覆寫HashMap中的get方法,通過getEntry方法擷取Entry對象。//注意這裡的recordAccess方法,//如果鏈表中元素的定序是按照插入的先後順序排序的話,該方法什麼也不做,//如果鏈表中元素的定序是按照訪問的先後順序排序的話,則將e移到鏈表的末尾處。    public V get(Object key) {        Entry<K,V> e = (Entry<K,V>)getEntry(key);        if (e == null)            return null;        e.recordAccess(this);        return e.value;    }//清空HashMap,並將雙向鏈表還原為只有頭結點的空鏈表    public void clear() {        super.clear();        header.before = header.after = header;    }//Enty的資料結構,多了兩個指向前後節點的引用    private static class Entry<K,V> extends HashMap.Entry<K,V> {        // These fields comprise the doubly linked list used for iteration.        Entry<K,V> before, after;//調用父類的構造方法Entry(int hash, K key, V value, HashMap.Entry<K,V> next) {            super(hash, key, value, next);        }//雙向迴圈鏈表中,刪除當前的Entry        private void remove() {            before.after = after;            after.before = before;        }//雙向迴圈立鏈表中,將當前的Entry插入到existingEntry的前面        private void addBefore(Entry<K,V> existingEntry) {            after  = existingEntry;            before = existingEntry.before;            before.after = this;            after.before = this;        }//覆寫HashMap中的recordAccess方法(HashMap中該方法為空白),//當調用父類的put方法,在發現插入的key已經存在時,會調用該方法,//調用LinkedHashmap覆寫的get方法時,也會調用到該方法,//該方法提供了LRU演算法的實現,它將最近使用的Entry放到雙向迴圈鏈表的尾部,//accessOrder為true時,get方法會調用recordAccess方法//put方法在覆蓋key-value對時也會調用recordAccess方法//它們導致Entry最近使用,因此將其移到雙向鏈表的末尾        void recordAccess(HashMap<K,V> m) {            LinkedHashMap<K,V> lm = (LinkedHashMap<K,V>)m;//如果鏈表中元素按照訪問順序排序,則將當前訪問的Entry移到雙向迴圈鏈表的尾部,//如果是按照插入的先後順序排序,則不做任何事情。            if (lm.accessOrder) {                lm.modCount++;//移除當前訪問的Entry                remove();//將當前訪問的Entry插入到鏈表的尾部                addBefore(lm.header);            }        }        void recordRemoval(HashMap<K,V> m) {            remove();        }    }//迭代器    private abstract class LinkedHashIterator<T> implements Iterator<T> {Entry<K,V> nextEntry    = header.after;Entry<K,V> lastReturned = null;/** * The modCount value that the iterator believes that the backing * List should have.  If this expectation is violated, the iterator * has detected concurrent modification. */int expectedModCount = modCount;public boolean hasNext() {            return nextEntry != header;}public void remove() {    if (lastReturned == null)throw new IllegalStateException();    if (modCount != expectedModCount)throw new ConcurrentModificationException();            LinkedHashMap.this.remove(lastReturned.key);            lastReturned = null;            expectedModCount = modCount;}//從head的下一個節點開始迭代Entry<K,V> nextEntry() {    if (modCount != expectedModCount)throw new ConcurrentModificationException();            if (nextEntry == header)                throw new NoSuchElementException();            Entry<K,V> e = lastReturned = nextEntry;            nextEntry = e.after;            return e;}    }//key迭代器    private class KeyIterator extends LinkedHashIterator<K> {public K next() { return nextEntry().getKey(); }    }//value迭代器    private class ValueIterator extends LinkedHashIterator<V> {public V next() { return nextEntry().value; }    }//Entry迭代器    private class EntryIterator extends LinkedHashIterator<Map.Entry<K,V>> {public Map.Entry<K,V> next() { return nextEntry(); }    }    // These Overrides alter the behavior of superclass view iterator() methods    Iterator<K> newKeyIterator()   { return new KeyIterator();   }    Iterator<V> newValueIterator() { return new ValueIterator(); }    Iterator<Map.Entry<K,V>> newEntryIterator() { return new EntryIterator(); }//覆寫HashMap中的addEntry方法,LinkedHashmap並沒有覆寫HashMap中的put方法,//而是覆寫了put方法所調用的addEntry方法和recordAccess方法,//put方法在插入的key已存在的情況下,會調用recordAccess方法,//在插入的key不存在的情況下,要調用addEntry插入新的Entry    void addEntry(int hash, K key, V value, int bucketIndex) {//建立新的Entry,並插入到LinkedHashMap中        createEntry(hash, key, value, bucketIndex);        //雙向鏈表的第一個有效節點(header後的那個節點)為近期最少使用的節點        Entry<K,V> eldest = header.after;//如果有必要,則刪除掉該近期最少使用的節點,//這要看對removeEldestEntry的覆寫,由於預設為false,因此預設是不做任何處理的。        if (removeEldestEntry(eldest)) {            removeEntryForKey(eldest.key);        } else {//擴容到原來的2倍            if (size >= threshold)                resize(2 * table.length);        }    }    void createEntry(int hash, K key, V value, int bucketIndex) {//建立新的Entry,並將其插入到數組對應槽的單鏈表的頭結點處,這點與HashMap中相同        HashMap.Entry<K,V> old = table[bucketIndex];Entry<K,V> e = new Entry<K,V>(hash, key, value, old);        table[bucketIndex] = e;//每次插入Entry時,都將其移到雙向鏈表的尾部,//這便會按照Entry插入LinkedHashMap的先後順序來迭代元素,//同時,新put進來的Entry是最近訪問的Entry,把其放在鏈表末尾 ,符合LRU演算法的實現        e.addBefore(header);        size++;    }//該方法是用來被覆寫的,一般如果用LinkedHashmap實現LRU演算法,就要覆寫該方法,//比如可以將該方法覆寫為如果設定的記憶體已滿,則返回true,這樣當再次向LinkedHashMap中put//Entry時,在調用的addEntry方法中便會將近期最少使用的節點刪除掉(header後的那個節點)。    protected boolean removeEldestEntry(Map.Entry<K,V> eldest) {        return false;    }}
    幾點總結

    關於LinkedHashMap的源碼,給出以下幾點比較重要的總結:

    1、從源碼中可以看出,LinkedHashMap中加入了一個head頭結點,將所有插入到該LinkedHashMap中的Entry按照插入的先後順序依次加入到以head為頭結點的雙向迴圈鏈表的尾部。


    實際上就是HashMap和LinkedList兩個集合類的儲存結構的結合。在LinkedHashMapMap中,所有put進來的Entry都儲存在如第一個圖所示的雜湊表中,但它又額外定義了一個以head為頭結點的空的雙向迴圈鏈表,每次put進來Entry,除了將其儲存到對雜湊表中對應的位置上外,還要將其插入到雙向迴圈鏈表的尾部。

    2、LinkedHashMap由於繼承自HashMap,因此它具有HashMap的所有特性,同樣允許key和value為null。

    3、注意源碼中的accessOrder標誌位,當它false時,表示雙向鏈表中的元素按照Entry插入LinkedHashMap到中的先後順序排序,即每次put到LinkedHashMap中的Entry都放在雙向鏈表的尾部,這樣遍曆雙向鏈表時,Entry的輸出順序便和插入的順序一致,這也是預設的雙向鏈表的儲存順序;當它為true時,表示雙向鏈表中的元素按照訪問的先後順序排列,可以看到,雖然Entry插入鏈表的順序依然是按照其put到LinkedHashMap中的順序,但put和get方法均有調用recordAccess方法(put方法在key相同,覆蓋原有的Entry的情況下調用recordAccess方法),該方法判斷accessOrder是否為true,如果是,則將當前訪問的Entry(put進來的Entry或get出來的Entry)移到雙向鏈表的尾部(key不相同時,put新Entry時,會調用addEntry,它會調用creatEntry,該方法同樣將新插入的元素放入到雙向鏈表的尾部,既符合插入的先後順序,又符合訪問的先後順序,因為這時該Entry也被訪問了),否則,什麼也不做。

    4、注意構造方法,前四個構造方法都將accessOrder設為false,說明預設是按照插入順序排序的,而第五個構造方法可以自訂傳入的accessOrder的值,因此可以指定雙向迴圈鏈表中元素的定序,一般要用LinkedHashMap實現LRU演算法,就要用該構造方法,將accessOrder置為true。

    5、LinkedHashMap並沒有覆寫HashMap中的put方法,而是覆寫了put方法中調用的addEntry方法和recordAccess方法,我們回過頭來再看下HashMap的put方法:

    // 將“key-value”添加到HashMap中        public V put(K key, V value) {            // 若“key為null”,則將該索引值對添加到table[0]中。            if (key == null)                return putForNullKey(value);            // 若“key不為null”,則計算該key的雜湊值,然後將其添加到該雜湊值對應的鏈表中。            int hash = hash(key.hashCode());            int i = indexFor(hash, table.length);            for (Entry<K,V> e = table[i]; e != null; e = e.next) {                Object k;                // 若“該key”對應的索引值對已經存在,則用新的value取代舊的value。然後退出!                if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {                    V oldValue = e.value;                    e.value = value;                    e.recordAccess(this);                    return oldValue;                }            }               // 若“該key”對應的索引值對不存在,則將“key-value”添加到table中            modCount++;          //將key-value添加到table[i]處          addEntry(hash, key, value, i);            return null;        }       
    當要put進來的Entry的key在雜湊表中已經在存在時,會調用recordAccess方法,當該key不存在時,則會調用addEntry方法將新的Entry插入到對應槽的單鏈表的頭部。

    我們先來看recordAccess方法:

//覆寫HashMap中的recordAccess方法(HashMap中該方法為空白),//當調用父類的put方法,在發現插入的key已經存在時,會調用該方法,//調用LinkedHashmap覆寫的get方法時,也會調用到該方法,//該方法提供了LRU演算法的實現,它將最近使用的Entry放到雙向迴圈鏈表的尾部,//accessOrder為true時,get方法會調用recordAccess方法//put方法在覆蓋key-value對時也會調用recordAccess方法//它們導致Entry最近使用,因此將其移到雙向鏈表的末尾        void recordAccess(HashMap<K,V> m) {            LinkedHashMap<K,V> lm = (LinkedHashMap<K,V>)m;//如果鏈表中元素按照訪問順序排序,則將當前訪問的Entry移到雙向迴圈鏈表的尾部,//如果是按照插入的先後順序排序,則不做任何事情。            if (lm.accessOrder) {                lm.modCount++;//移除當前訪問的Entry                remove();//將當前訪問的Entry插入到鏈表的尾部                addBefore(lm.header);            }        }
    該方法會判斷accessOrder是否為true,如果為true,它會將當前訪問的Entry(在這裡指put進來的Entry)移動到雙向迴圈鏈表的尾部,從而實現雙向鏈表中的元素按照訪問順序來排序(最近訪問的Entry放到鏈表的最後,這樣多次下來,前面就是最近沒有被訪問的元素,在實現、LRU演算法時,當雙向鏈表中的節點數達到最大值時,將前面的元素刪去即可,因為前面的元素是最近最少使用的),否則什麼也不做。
    再來看addEntry方法:

//覆寫HashMap中的addEntry方法,LinkedHashmap並沒有覆寫HashMap中的put方法,//而是覆寫了put方法所調用的addEntry方法和recordAccess方法,//put方法在插入的key已存在的情況下,會調用recordAccess方法,//在插入的key不存在的情況下,要調用addEntry插入新的Entry    void addEntry(int hash, K key, V value, int bucketIndex) {//建立新的Entry,並插入到LinkedHashMap中        createEntry(hash, key, value, bucketIndex);        //雙向鏈表的第一個有效節點(header後的那個節點)為近期最少使用的節點        Entry<K,V> eldest = header.after;//如果有必要,則刪除掉該近期最少使用的節點,//這要看對removeEldestEntry的覆寫,由於預設為false,因此預設是不做任何處理的。        if (removeEldestEntry(eldest)) {            removeEntryForKey(eldest.key);        } else {//擴容到原來的2倍            if (size >= threshold)                resize(2 * table.length);        }    }    void createEntry(int hash, K key, V value, int bucketIndex) {//建立新的Entry,並將其插入到數組對應槽的單鏈表的頭結點處,這點與HashMap中相同        HashMap.Entry<K,V> old = table[bucketIndex];Entry<K,V> e = new Entry<K,V>(hash, key, value, old);        table[bucketIndex] = e;//每次插入Entry時,都將其移到雙向鏈表的尾部,//這便會按照Entry插入LinkedHashMap的先後順序來迭代元素,//同時,新put進來的Entry是最近訪問的Entry,把其放在鏈表末尾 ,符合LRU演算法的實現        e.addBefore(header);        size++;    }
    同樣是將新的Entry插入到table中對應槽所對應單鏈表的頭結點中,但可以看出,在createEntry中,同樣把新put進來的Entry插入到了雙向鏈表的尾部,從插入順序的層面來說,新的Entry插入到雙向鏈表的尾部,可以實現按照插入的先後順序來迭代Entry,而從訪問順序的層面來說,新put進來的Entry又是最近訪問的Entry,也應該將其放在雙向鏈表的尾部。

    上面還有個removeEldestEntry方法,該方法如下:

//該方法是用來被覆寫的,一般如果用LinkedHashmap實現LRU演算法,就要覆寫該方法,//比如可以將該方法覆寫為如果設定的記憶體已滿,則返回true,這樣當再次向LinkedHashMap中put//Entry時,在調用的addEntry方法中便會將近期最少使用的節點刪除掉(header後的那個節點)。    protected boolean removeEldestEntry(Map.Entry<K,V> eldest) {        return false;    }}
    該方法預設返回false,我們一般在用LinkedHashMap實現LRU演算法時,要覆寫該方法,一般的實現是,當設定的記憶體(這裡指節點個數)達到最大值時,返回true,這樣put新的Entry(該Entry的key在雜湊表中沒有已經存在)時,就會調用removeEntryForKey方法,將最近最少使用的節點刪除(head後面的那個節點,實際上是最近沒有使用)。
    6、LinkedHashMap覆寫了HashMap的get方法:

//覆寫HashMap中的get方法,通過getEntry方法擷取Entry對象。//注意這裡的recordAccess方法,//如果鏈表中元素的定序是按照插入的先後順序排序的話,該方法什麼也不做,//如果鏈表中元素的定序是按照訪問的先後順序排序的話,則將e移到鏈表的末尾處。    public V get(Object key) {        Entry<K,V> e = (Entry<K,V>)getEntry(key);        if (e == null)            return null;        e.recordAccess(this);        return e.value;    }
    先取得Entry,如果不為null,一樣調用recordAccess方法,上面已經說得很清楚,這裡不在多解釋了。

    7、最後說說LinkedHashMap是如何?LRU的。首先,當accessOrder為true時,才會開啟按訪問順序排序的模式,才能用來實現LRU演算法。我們可以看到,無論是put方法還是get方法,都會導致目標Entry成為最近訪問的Entry,因此便把該Entry加入到了雙向鏈表的末尾(get方法通過調用recordAccess方法來實現,put方法在覆蓋已有key的情況下,也是通過調用recordAccess方法來實現,在插入新的Entry時,則是通過createEntry中的addBefore方法來實現),這樣便把最近使用了的Entry放入到了雙向鏈表的後面,多次操作後,雙向鏈表前面的Entry便是最近沒有使用的,這樣當節點個數滿的時候,刪除的最前面的Entry(head後面的那個Entry)便是最近最少使用的Entry。

    

  
java原始碼分析

awt的,哥們用java做CS有耐性。。。
public class userRegister implements ActionListener {
ArrayList<User> list = new ArrayList<User>();
JTextField jtf1 = new JTextField(14);
JTextField jtf2 = new JTextField(14);
JTextField jtf3 = new JTextField(14);
JTextField jtf4 = new JTextField(14);
JFrame jf;
public userRegister() throws Exception {
createGUI();
}
這些代碼都誰也不挨著誰,這是誰拿這些代碼整你。。。。。
 
java中LinkedHashMap的LRU演算法,為何多次訪問其實並未改變元素的實際排序

hashMap.get(2) 不是第三個元素!!唉
 

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在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.