Java集合框架源碼分析之LinkedHashMap詳解
LinkedHashMap簡介
LinkedHashMap是HashMap的子類,與HashMap有著同樣的存儲結(jié)構(gòu),但它加入了一個(gè)雙向鏈表的頭結(jié)點(diǎn),將所有put到LinkedHashmap的節(jié)點(diǎn)一一串成了一個(gè)雙向循環(huán)鏈表,因此它保留了節(jié)點(diǎn)插入的順序,可以使節(jié)點(diǎn)的輸出順序與輸入順序相同。
LinkedHashMap可以用來實(shí)現(xiàn)LRU算法(這會在下面的源碼中進(jìn)行分析)。
LinkedHashMap同樣是非線程安全的,只在單線程環(huán)境下使用。
LinkedHashMap源碼剖析
LinkedHashMap源碼如下(加入了詳細(xì)的注釋):
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; //雙向循環(huán)鏈表的頭結(jié)點(diǎn),整個(gè)LinkedHashMap中只有一個(gè)header, //它將哈希表中所有的Entry貫穿起來,header中不保存key-value對,只保存前后節(jié)點(diǎn)的引用 private transient Entry<K,V> header; //雙向鏈表中元素排序規(guī)則的標(biāo)志位。 //accessOrder為false,表示按插入順序排序 //accessOrder為true,表示按訪問順序排序 private final boolean accessOrder; //調(diào)用HashMap的構(gòu)造方法來構(gòu)造底層的數(shù)組 public LinkedHashMap(int initialCapacity, float loadFactor) { super(initialCapacity, loadFactor); accessOrder = false; //鏈表中的元素默認(rèn)按照插入順序排序 } //加載因子取默認(rèn)的0.75f public LinkedHashMap(int initialCapacity) { super(initialCapacity); accessOrder = false; } //加載因子取默認(rèn)的0.75f,容量取默認(rèn)的16 public LinkedHashMap() { super(); accessOrder = false; } //含有子Map的構(gòu)造方法,同樣調(diào)用HashMap的對應(yīng)的構(gòu)造方法 public LinkedHashMap(Map<? extends K, ? extends V> m) { super(m); accessOrder = false; } //該構(gòu)造方法可以指定鏈表中的元素排序的規(guī)則 public LinkedHashMap(int initialCapacity,float loadFactor,boolean accessOrder) { super(initialCapacity, loadFactor); this.accessOrder = accessOrder; } //覆寫父類的init()方法(HashMap中的init方法為空), //該方法在父類的構(gòu)造方法和Clone、readObject中在插入元素前被調(diào)用, //初始化一個(gè)空的雙向循環(huán)鏈表,頭結(jié)點(diǎn)中不保存數(shù)據(jù),頭結(jié)點(diǎn)的下一個(gè)節(jié)點(diǎn)才開始保存數(shù)據(jù)。 void init() { header = new Entry<K,V>(-1, null, null, null); header.before = header.after = header; } //覆寫HashMap中的transfer方法,它在父類的resize方法中被調(diào)用, //擴(kuò)容后,將key-value對重新映射到新的newTable中 //覆寫該方法的目的是為了提高復(fù)制的效率, //這里充分利用雙向循環(huán)鏈表的特點(diǎn)進(jìn)行迭代,不用對底層的數(shù)組進(jìn)行for循環(huán)。 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方法, //覆寫該方法的目的同樣是為了提高查詢的效率, //利用雙向循環(huán)鏈表的特點(diǎn)進(jìn)行查詢,少了對數(shù)組的外層for循環(huán) 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方法, //如果鏈表中元素的排序規(guī)則是按照插入的先后順序排序的話,該方法什么也不做, //如果鏈表中元素的排序規(guī)則是按照訪問的先后順序排序的話,則將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,并將雙向鏈表還原為只有頭結(jié)點(diǎn)的空鏈表 public void clear() { super.clear(); header.before = header.after = header; } //Enty的數(shù)據(jù)結(jié)構(gòu),多了兩個(gè)指向前后節(jié)點(diǎn)的引用 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; //調(diào)用父類的構(gòu)造方法 Entry(int hash, K key, V value, HashMap.Entry<K,V> next) { super(hash, key, value, next); } //雙向循環(huán)鏈表中,刪除當(dāng)前的Entry private void remove() { before.after = after; after.before = before; } //雙向循環(huán)立鏈表中,將當(dāng)前的Entry插入到existingEntry的前面 private void addBefore(Entry<K,V> existingEntry) { after = existingEntry; before = existingEntry.before; before.after = this; after.before = this; } //覆寫HashMap中的recordAccess方法(HashMap中該方法為空), //當(dāng)調(diào)用父類的put方法,在發(fā)現(xiàn)插入的key已經(jīng)存在時(shí),會調(diào)用該方法, //調(diào)用LinkedHashmap覆寫的get方法時(shí),也會調(diào)用到該方法, //該方法提供了LRU算法的實(shí)現(xiàn),它將最近使用的Entry放到雙向循環(huán)鏈表的尾部, //accessOrder為true時(shí),get方法會調(diào)用recordAccess方法 //put方法在覆蓋key-value對時(shí)也會調(diào)用recordAccess方法 //它們導(dǎo)致Entry最近使用,因此將其移到雙向鏈表的末尾 void recordAccess(HashMap<K,V> m) { LinkedHashMap<K,V> lm = (LinkedHashMap<K,V>)m; //如果鏈表中元素按照訪問順序排序,則將當(dāng)前訪問的Entry移到雙向循環(huán)鏈表的尾部, //如果是按照插入的先后順序排序,則不做任何事情。 if (lm.accessOrder) { lm.modCount++; //移除當(dāng)前訪問的Entry remove(); //將當(dāng)前訪問的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的下一個(gè)節(jié)點(diǎn)開始迭代 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方法所調(diào)用的addEntry方法和recordAccess方法, //put方法在插入的key已存在的情況下,會調(diào)用recordAccess方法, //在插入的key不存在的情況下,要調(diào)用addEntry插入新的Entry void addEntry(int hash, K key, V value, int bucketIndex) { //創(chuàng)建新的Entry,并插入到LinkedHashMap中 createEntry(hash, key, value, bucketIndex); //雙向鏈表的第一個(gè)有效節(jié)點(diǎn)(header后的那個(gè)節(jié)點(diǎn))為近期最少使用的節(jié)點(diǎn) Entry<K,V> eldest = header.after; //如果有必要,則刪除掉該近期最少使用的節(jié)點(diǎn), //這要看對removeEldestEntry的覆寫,由于默認(rèn)為false,因此默認(rèn)是不做任何處理的。 if (removeEldestEntry(eldest)) { removeEntryForKey(eldest.key); } else { //擴(kuò)容到原來的2倍 if (size >= threshold) resize(2 * table.length); } } void createEntry(int hash, K key, V value, int bucketIndex) { //創(chuàng)建新的Entry,并將其插入到數(shù)組對應(yīng)槽的單鏈表的頭結(jié)點(diǎn)處,這點(diǎn)與HashMap中相同 HashMap.Entry<K,V> old = table[bucketIndex]; Entry<K,V> e = new Entry<K,V>(hash, key, value, old); table[bucketIndex] = e; //每次插入Entry時(shí),都將其移到雙向鏈表的尾部, //這便會按照Entry插入LinkedHashMap的先后順序來迭代元素, //同時(shí),新put進(jìn)來的Entry是最近訪問的Entry,把其放在鏈表末尾 ,符合LRU算法的實(shí)現(xiàn) e.addBefore(header); size++; } //該方法是用來被覆寫的,一般如果用LinkedHashmap實(shí)現(xiàn)LRU算法,就要覆寫該方法, //比如可以將該方法覆寫為如果設(shè)定的內(nèi)存已滿,則返回true,這樣當(dāng)再次向LinkedHashMap中put //Entry時(shí),在調(diào)用的addEntry方法中便會將近期最少使用的節(jié)點(diǎn)刪除掉(header后的那個(gè)節(jié)點(diǎn))。 protected boolean removeEldestEntry(Map.Entry<K,V> eldest) { return false; } }
總結(jié)
關(guān)于LinkedHashMap的源碼,給出以下幾點(diǎn)比較重要的總結(jié):
1、從源碼中可以看出,LinkedHashMap中加入了一個(gè)head頭結(jié)點(diǎn),將所有插入到該LinkedHashMap中的Entry按照插入的先后順序依次加入到以head為頭結(jié)點(diǎn)的雙向循環(huán)鏈表的尾部。
1、實(shí)際上就是HashMap和LinkedList兩個(gè)集合類的存儲結(jié)構(gòu)的結(jié)合。在LinkedHashMapMap中,所有put進(jìn)來的Entry都保存在哈希表中,但它又額外定義了一個(gè)以head為頭結(jié)點(diǎn)的空的雙向循環(huán)鏈表,每次put進(jìn)來Entry,除了將其保存到對哈希表中對應(yīng)的位置上外,還要將其插入到雙向循環(huán)鏈表的尾部。
2、LinkedHashMap由于繼承自HashMap,因此它具有HashMap的所有特性,同樣允許key和value為null。
3、注意源碼中的accessOrder標(biāo)志位,當(dāng)它false時(shí),表示雙向鏈表中的元素按照Entry插入LinkedHashMap到中的先后順序排序,即每次put到LinkedHashMap中的Entry都放在雙向鏈表的尾部,這樣遍歷雙向鏈表時(shí),Entry的輸出順序便和插入的順序一致,這也是默認(rèn)的雙向鏈表的存儲順序;當(dāng)它為true時(shí),表示雙向鏈表中的元素按照訪問的先后順序排列,可以看到,雖然Entry插入鏈表的順序依然是按照其put到LinkedHashMap中的順序,但put和get方法均有調(diào)用recordAccess方法(put方法在key相同,覆蓋原有的Entry的情況下調(diào)用recordAccess方法),該方法判斷accessOrder是否為true,如果是,則將當(dāng)前訪問的Entry(put進(jìn)來的Entry或get出來的Entry)移到雙向鏈表的尾部(key不相同時(shí),put新Entry時(shí),會調(diào)用addEntry,它會調(diào)用creatEntry,該方法同樣將新插入的元素放入到雙向鏈表的尾部,既符合插入的先后順序,又符合訪問的先后順序,因?yàn)檫@時(shí)該Entry也被訪問了),否則,什么也不做。
4、注意構(gòu)造方法,前四個(gè)構(gòu)造方法都將accessOrder設(shè)為false,說明默認(rèn)是按照插入順序排序的,而第五個(gè)構(gòu)造方法可以自定義傳入的accessOrder的值,因此可以指定雙向循環(huán)鏈表中元素的排序規(guī)則,一般要用LinkedHashMap實(shí)現(xiàn)LRU算法,就要用該構(gòu)造方法,將accessOrder置為true。
5、LinkedHashMap并沒有覆寫HashMap中的put方法,而是覆寫了put方法中調(diào)用的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”,則計(jì)算該key的哈希值,然后將其添加到該哈希值對應(yīng)的鏈表中。 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”對應(yīng)的鍵值對已經(jīng)存在,則用新的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”對應(yīng)的鍵值對不存在,則將“key-value”添加到table中 modCount++; //將key-value添加到table[i]處 addEntry(hash, key, value, i); return null; }
當(dāng)要put進(jìn)來的Entry的key在哈希表中已經(jīng)在存在時(shí),會調(diào)用recordAccess方法,當(dāng)該key不存在時(shí),則會調(diào)用addEntry方法將新的Entry插入到對應(yīng)槽的單鏈表的頭部。
我們先來看recordAccess方法:
//覆寫HashMap中的recordAccess方法(HashMap中該方法為空), //當(dāng)調(diào)用父類的put方法,在發(fā)現(xiàn)插入的key已經(jīng)存在時(shí),會調(diào)用該方法, //調(diào)用LinkedHashmap覆寫的get方法時(shí),也會調(diào)用到該方法, //該方法提供了LRU算法的實(shí)現(xiàn),它將最近使用的Entry放到雙向循環(huán)鏈表的尾部, //accessOrder為true時(shí),get方法會調(diào)用recordAccess方法 //put方法在覆蓋key-value對時(shí)也會調(diào)用recordAccess方法 //它們導(dǎo)致Entry最近使用,因此將其移到雙向鏈表的末尾 void recordAccess(HashMap<K,V> m) { LinkedHashMap<K,V> lm = (LinkedHashMap<K,V>)m; //如果鏈表中元素按照訪問順序排序,則將當(dāng)前訪問的Entry移到雙向循環(huán)鏈表的尾部, //如果是按照插入的先后順序排序,則不做任何事情。 if (lm.accessOrder) { lm.modCount++; //移除當(dāng)前訪問的Entry remove(); //將當(dāng)前訪問的Entry插入到鏈表的尾部 addBefore(lm.header); } }
該方法會判斷accessOrder是否為true,如果為true,它會將當(dāng)前訪問的Entry(在這里指put進(jìn)來的Entry)移動到雙向循環(huán)鏈表的尾部,從而實(shí)現(xiàn)雙向鏈表中的元素按照訪問順序來排序(最近訪問的Entry放到鏈表的最后,這樣多次下來,前面就是最近沒有被訪問的元素,在實(shí)現(xiàn)、LRU算法時(shí),當(dāng)雙向鏈表中的節(jié)點(diǎn)數(shù)達(dá)到最大值時(shí),將前面的元素刪去即可,因?yàn)榍懊娴脑厥亲罱钌偈褂玫模?,否則什么也不做。
再來看addEntry方法:
//覆寫HashMap中的addEntry方法,LinkedHashmap并沒有覆寫HashMap中的put方法, //而是覆寫了put方法所調(diào)用的addEntry方法和recordAccess方法, //put方法在插入的key已存在的情況下,會調(diào)用recordAccess方法, //在插入的key不存在的情況下,要調(diào)用addEntry插入新的Entry void addEntry(int hash, K key, V value, int bucketIndex) { //創(chuàng)建新的Entry,并插入到LinkedHashMap中 createEntry(hash, key, value, bucketIndex); //雙向鏈表的第一個(gè)有效節(jié)點(diǎn)(header后的那個(gè)節(jié)點(diǎn))為近期最少使用的節(jié)點(diǎn) Entry<K,V> eldest = header.after; //如果有必要,則刪除掉該近期最少使用的節(jié)點(diǎn), //這要看對removeEldestEntry的覆寫,由于默認(rèn)為false,因此默認(rèn)是不做任何處理的。 if (removeEldestEntry(eldest)) { removeEntryForKey(eldest.key); } else { //擴(kuò)容到原來的2倍 if (size >= threshold) resize(2 * table.length); } } void createEntry(int hash, K key, V value, int bucketIndex) { //創(chuàng)建新的Entry,并將其插入到數(shù)組對應(yīng)槽的單鏈表的頭結(jié)點(diǎn)處,這點(diǎn)與HashMap中相同 HashMap.Entry<K,V> old = table[bucketIndex]; Entry<K,V> e = new Entry<K,V>(hash, key, value, old); table[bucketIndex] = e; //每次插入Entry時(shí),都將其移到雙向鏈表的尾部, //這便會按照Entry插入LinkedHashMap的先后順序來迭代元素, //同時(shí),新put進(jìn)來的Entry是最近訪問的Entry,把其放在鏈表末尾 ,符合LRU算法的實(shí)現(xiàn) e.addBefore(header); size++; }
同樣是將新的Entry插入到table中對應(yīng)槽所對應(yīng)單鏈表的頭結(jié)點(diǎn)中,但可以看出,在createEntry中,同樣把新put進(jìn)來的Entry插入到了雙向鏈表的尾部,從插入順序的層面來說,新的Entry插入到雙向鏈表的尾部,可以實(shí)現(xiàn)按照插入的先后順序來迭代Entry,而從訪問順序的層面來說,新put進(jìn)來的Entry又是最近訪問的Entry,也應(yīng)該將其放在雙向鏈表的尾部。
上面還有個(gè)removeEldestEntry方法,該方法如下:
//該方法是用來被覆寫的,一般如果用LinkedHashmap實(shí)現(xiàn)LRU算法,就要覆寫該方法, //比如可以將該方法覆寫為如果設(shè)定的內(nèi)存已滿,則返回true,這樣當(dāng)再次向LinkedHashMap中put //Entry時(shí),在調(diào)用的addEntry方法中便會將近期最少使用的節(jié)點(diǎn)刪除掉(header后的那個(gè)節(jié)點(diǎn))。 protected boolean removeEldestEntry(Map.Entry<K,V> eldest) { return false; } }
該方法默認(rèn)返回false,我們一般在用LinkedHashMap實(shí)現(xiàn)LRU算法時(shí),要覆寫該方法,一般的實(shí)現(xiàn)是,當(dāng)設(shè)定的內(nèi)存(這里指節(jié)點(diǎn)個(gè)數(shù))達(dá)到最大值時(shí),返回true,這樣put新的Entry(該Entry的key在哈希表中沒有已經(jīng)存在)時(shí),就會調(diào)用removeEntryForKey方法,將最近最少使用的節(jié)點(diǎn)刪除(head后面的那個(gè)節(jié)點(diǎn),實(shí)際上是最近沒有使用)。
6、LinkedHashMap覆寫了HashMap的get方法:
//覆寫HashMap中的get方法,通過getEntry方法獲取Entry對象。 //注意這里的recordAccess方法, //如果鏈表中元素的排序規(guī)則是按照插入的先后順序排序的話,該方法什么也不做, //如果鏈表中元素的排序規(guī)則是按照訪問的先后順序排序的話,則將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,一樣調(diào)用recordAccess方法,上面已經(jīng)說得很清楚,這里不在多解釋了。
7、最后說說LinkedHashMap是如何實(shí)現(xiàn)LRU的。
首先,當(dāng)accessOrder為true時(shí),才會開啟按訪問順序排序的模式,才能用來實(shí)現(xiàn)LRU算法。我們可以看到,無論是put方法還是get方法,都會導(dǎo)致目標(biāo)Entry成為最近訪問的Entry,因此便把該Entry加入到了雙向鏈表的末尾(get方法通過調(diào)用recordAccess方法來實(shí)現(xiàn),put方法在覆蓋已有key的情況下,也是通過調(diào)用recordAccess方法來實(shí)現(xiàn),在插入新的Entry時(shí),則是通過createEntry中的addBefore方法來實(shí)現(xiàn)),這樣便把最近使用了的Entry放入到了雙向鏈表的后面,多次操作后,雙向鏈表前面的Entry便是最近沒有使用的,這樣當(dāng)節(jié)點(diǎn)個(gè)數(shù)滿的時(shí)候,刪除的最前面的Entry(head后面的那個(gè)Entry)便是最近最少使用的Entry。
結(jié)束語
以上就是本文關(guān)于Java集合框架源碼分析之LinkedHashMap詳解的全部內(nèi)容,希望對大家學(xué)習(xí)Java能夠有所幫助。歡迎大家參閱本站其他專題,感謝大家讀腳本之家的支持!
相關(guān)文章
java.lang.NoClassDefFoundError錯(cuò)誤的原因及解決方法
這篇文章主要給大家介紹了關(guān)于java.lang.NoClassDefFoundError錯(cuò)誤的原因及解決的相關(guān)資料,java.lang.NoClassDefFoundError是Java虛擬機(jī)在運(yùn)行時(shí)無法找到特定類的錯(cuò)誤,需要的朋友可以參考下2023-10-10淺談java線程中生產(chǎn)者與消費(fèi)者的問題
下面小編就為大家?guī)硪黄獪\談java線程中生產(chǎn)者與消費(fèi)者的問題。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2016-07-07Spring注入Map集合實(shí)現(xiàn)策略模式詳解
這篇文章主要介紹了Spring注入Map集合實(shí)現(xiàn)策略模式詳解,Spring提供通過@Resource注解將相同類型的對象注入到Map集合,并將對象的名字作為key,對象作為value封裝進(jìn)入Map,需要的朋友可以參考下2023-11-11java性能調(diào)優(yōu)System的gc垃圾回收方法
這篇文章主要為大家介紹了java性能調(diào)優(yōu)System的gc垃圾回收方法示例解析有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步2022-03-03