轉載請注明出處:http://blog.csdn.net/ns_code/article/details/37867985
?
? ? 前言:有網友建議分析下LinkedHashMap的源碼,于是花了一晚上時間研究了下,分享出此文(這個系列的最后一篇博文了),希望大家相互學習。LinkedHashMap的源碼理解起來也不難(當然,要建立在對HashMap源碼有較好理解的基礎上)。
? ? LinkedHashMap簡介
? ? LinkedHashMap是HashMap的子類,與HashMap有著同樣的存儲結構,但它加入了一個雙向鏈表的頭結點,將所有put到LinkedHashmap的節點一一串成了一個雙向循環鏈表,因此它保留了節點插入的順序,可以使節點的輸出順序與輸入順序相同。
? ? LinkedHashMap可以用來實現LRU算法(這會在下面的源碼中進行分析)。
? ? LinkedHashMap同樣是非線程安全的,只在單線程環境下使用。
?
? ? LinkedHashMap源碼剖析
? ? LinkedHashMap源碼如下(加入了詳細的注釋):
[java] view plaincopy
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;????????????????private?transient?Entry<K,V>?header;????????????????????private?final?boolean?accessOrder;????????????public?LinkedHashMap(int?initialCapacity,?float?loadFactor)?{??????????super(initialCapacity,?loadFactor);??????????accessOrder?=?false;????????}????????????public?LinkedHashMap(int?initialCapacity)?{??????????super(initialCapacity);??????????accessOrder?=?false;??????}????????????public?LinkedHashMap()?{??????????super();??????????accessOrder?=?false;??????}????????????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;??????}????????????????????void?init()?{??????????header?=?new?Entry<K,V>(-1,?null,?null,?null);??????????header.before?=?header.after?=?header;??????}??????????????????????????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;??????????}??????}??????????????????????public?boolean?containsValue(Object?value)?{??????????????????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;??????}??????????????????????????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;??????}????????????public?void?clear()?{??????????super.clear();??????????header.before?=?header.after?=?header;??????}????????????private?static?class?Entry<K,V>?extends?HashMap.Entry<K,V>?{??????????????????Entry<K,V>?before,?after;????????????????????Entry(int?hash,?K?key,?V?value,?HashMap.Entry<K,V>?next)?{??????????????super(hash,?key,?value,?next);??????????}????????????????????private?void?remove()?{??????????????before.after?=?after;??????????????after.before?=?before;??????????}????????????????????private?void?addBefore(Entry<K,V>?existingEntry)?{??????????????after??=?existingEntry;??????????????before?=?existingEntry.before;??????????????before.after?=?this;??????????????after.before?=?this;??????????}??????????????????????????????????????????????????????????????????????void?recordAccess(HashMap<K,V>?m)?{??????????????LinkedHashMap<K,V>?lm?=?(LinkedHashMap<K,V>)m;??????????????????????????????????????if?(lm.accessOrder)?{??????????????????lm.modCount++;??????????????????????????????????remove();??????????????????????????????????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;????????????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;??????}????????????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;??????}??????}????????????private?class?KeyIterator?extends?LinkedHashIterator<K>?{??????public?K?next()?{?return?nextEntry().getKey();?}??????}????????????private?class?ValueIterator?extends?LinkedHashIterator<V>?{??????public?V?next()?{?return?nextEntry().value;?}??????}????????????private?class?EntryIterator?extends?LinkedHashIterator<Map.Entry<K,V>>?{??????public?Map.Entry<K,V>?next()?{?return?nextEntry();?}??????}????????????Iterator<K>?newKeyIterator()???{?return?new?KeyIterator();???}??????Iterator<V>?newValueIterator()?{?return?new?ValueIterator();?}??????Iterator<Map.Entry<K,V>>?newEntryIterator()?{?return?new?EntryIterator();?}??????????????????????????void?addEntry(int?hash,?K?key,?V?value,?int?bucketIndex)?{??????????????????createEntry(hash,?key,?value,?bucketIndex);????????????????????Entry<K,V>?eldest?=?header.after;??????????????????????????if?(removeEldestEntry(eldest))?{??????????????removeEntryForKey(eldest.key);??????????}?else?{??????????????????????????if?(size?>=?threshold)??????????????????resize(2?*?table.length);??????????}??????}????????void?createEntry(int?hash,?K?key,?V?value,?int?bucketIndex)?{??????????????????HashMap.Entry<K,V>?old?=?table[bucketIndex];??????????Entry<K,V>?e?=?new?Entry<K,V>(hash,?key,?value,?old);??????????table[bucketIndex]?=?e;??????????????????????????????????e.addBefore(header);??????????size++;??????}????????????????????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方法:
[java] view plaincopy
public?V?put(K?key,?V?value)?{??????????????if?(key?==?null)??????????????return?putForNullKey(value);??????????????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;??????????????????????if?(e.hash?==?hash?&&?((k?=?e.key)?==?key?||?key.equals(k)))?{??????????????????V?oldValue?=?e.value;??????????????????e.value?=?value;??????????????????e.recordAccess(this);??????????????????return?oldValue;??????????????}??????????}????????????????modCount++;????????????addEntry(hash,?key,?value,?i);??????????return?null;??????}?????? ? ? 當要put進來的Entry的key在哈希表中已經在存在時,會調用recordAccess方法,當該key不存在時,則會調用addEntry方法將新的Entry插入到對應槽的單鏈表的頭部。
? ? 我們先來看recordAccess方法:
[java] view plaincopy
??????void?recordAccess(HashMap<K,V>?m)?{????????????LinkedHashMap<K,V>?lm?=?(LinkedHashMap<K,V>)m;????????????????????if?(lm.accessOrder)?{????????????????lm.modCount++;????????????????????????remove();????????????????????????addBefore(lm.header);????????????}????????}?? ? ? 該方法會判斷accessOrder是否為true,如果為true,它會將當前訪問的Entry(在這里指put進來的Entry)移動到雙向循環鏈表的尾部,從而實現雙向鏈表中的元素按照訪問順序來排序(最近訪問的Entry放到鏈表的最后,這樣多次下來,前面就是最近沒有被訪問的元素,在實現、LRU算法時,當雙向鏈表中的節點數達到最大值時,將前面的元素刪去即可,因為前面的元素是最近最少使用的),否則什么也不做。
? ? 再來看addEntry方法:
[java] view plaincopy
???void?addEntry(int?hash,?K?key,?V?value,?int?bucketIndex)?{?????????????createEntry(hash,?key,?value,?bucketIndex);??????????????????Entry<K,V>?eldest?=?header.after;?????????????????if?(removeEldestEntry(eldest))?{?????????????removeEntryForKey(eldest.key);?????????}?else?{?????????????????????if?(size?>=?threshold)?????????????????resize(2?*?table.length);?????????}?????}???????void?createEntry(int?hash,?K?key,?V?value,?int?bucketIndex)?{?????????????HashMap.Entry<K,V>?old?=?table[bucketIndex];??????Entry<K,V>?e?=?new?Entry<K,V>(hash,?key,?value,?old);?????????table[bucketIndex]?=?e;?????????????????????e.addBefore(header);?????????size++;?????}?? ? ? 同樣是將新的Entry插入到table中對應槽所對應單鏈表的頭結點中,但可以看出,在createEntry中,同樣把新put進來的Entry插入到了雙向鏈表的尾部,從插入順序的層面來說,新的Entry插入到雙向鏈表的尾部,可以實現按照插入的先后順序來迭代Entry,而從訪問順序的層面來說,新put進來的Entry又是最近訪問的Entry,也應該將其放在雙向鏈表的尾部。
? ? 上面還有個removeEldestEntry方法,該方法如下:
[java] view plaincopy
????????????????protected?boolean?removeEldestEntry(Map.Entry<K,V>?eldest)?{??????????return?false;??????}??}?? ? ? 該方法默認返回false,我們一般在用LinkedHashMap實現LRU算法時,要覆寫該方法,一般的實現是,當設定的內存(這里指節點個數)達到最大值時,返回true,這樣put新的Entry(該Entry的key在哈希表中沒有已經存在)時,就會調用removeEntryForKey方法,將最近最少使用的節點刪除(head后面的那個節點,實際上是最近沒有使用)。
? ? 6、LinkedHashMap覆寫了HashMap的get方法:
[java] view plaincopy
???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。
轉載于:https://www.cnblogs.com/xuyatao/p/6917145.html
總結
以上是生活随笔為你收集整理的转:【Java集合源码剖析】LinkedHashmap源码剖析的全部內容,希望文章能夠幫你解決所遇到的問題。
如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。