《Java程式效能優化》學習筆記之HashMap和LinkedHashMap
1.HashMap沒啥可說的
2.LinkedHashMap是HashMap的子類,在HashMap的基礎上加了一個連結串列,每次put元素都會往連結串列上加節點。
public LinkedHashMap(int initialCapacity, float loadFactor,boolean accessOrder): accessOrder為true的時候按照元素最後訪問時間排序(LRU演算法:最近最久使用),為false則是按照插入順序排序,預設為false.
LinkedHashMap 的get和put方法對HashMap加了些改動,會調整LinkedHashMap的連結串列結構。
以下摘自http://blog.csdn.net/ns_code/article/details/37867985
關於LinkedHashMap的原始碼,給出以下幾點比較重要的總結:
1、從原始碼中可以看出,LinkedHashMap中加入了一個head頭結點,將所有插入到該LinkedHashMap中的Entry按照插入的先後順序依次加入到以head為頭結點的雙向迴圈連結串列的尾部。
實際上就是HashMap和LinkedList兩個集合類的儲存結構的結合。在LinkedHashMapMap中,所有put進來的Entry都儲存在如第一個圖所示的雜湊表中,但它又額外定義了一個以head為頭結點的空的雙向迴圈連結串列
2、LinkedHashMap由於繼承自HashMap,因此它具有HashMap的所有特性,同樣允許key和value為null。
3、注意原始碼中的accessOrder標誌位,當它false時,表示雙向連結串列中的元素按照Entry插入LinkedHashMap到中的先後順序排序,即每次put到LinkedHashMap中的Entry都放在雙向連結串列的尾部,這樣遍歷雙向連結串列時,Entry的輸出順序便和插入的順序一致,這也是預設的雙向連結串列的儲存順序;當它為true時,表示雙向連結串列中的元素按照訪問的先後順序排列
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);
- returnnull;
- }
我們先來看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);
- }
- }
再來看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++;
- }
上面還有個removeEldestEntry方法,該方法如下:
- //該方法是用來被覆寫的,一般如果用LinkedHashmap實現LRU演算法,就要覆寫該方法,
- //比如可以將該方法覆寫為如果設定的記憶體已滿,則返回true,這樣當再次向LinkedHashMap中put
- //Entry時,在呼叫的addEntry方法中便會將近期最少使用的節點刪除掉(header後的那個節點)。
- protectedboolean removeEldestEntry(Map.Entry<K,V> eldest) {
- returnfalse;
- }
- }
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)
- returnnull;
- e.recordAccess(this);
- return e.value;
- }
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。