摘要:源碼解析屬性雙向鏈表頭節點雙向鏈表尾節點是否按訪問順序排序雙向鏈表的頭節點,舊數據存在頭節點。雙向鏈表的尾節點,新數據存在尾節點。內部類位于中位于中存儲節點,繼承自的類,用于單鏈表存儲于桶中,和用于雙向鏈表存儲所有元素。
簡介
LinkedHashMap內部維護了一個雙向鏈表,能保證元素按插入的順序訪問,也能以訪問順序訪問,可以用來實現LRU緩存策略。
LinkedHashMap可以看成是 LinkedList + HashMap。
繼承體系LinkedHashMap繼承HashMap,擁有HashMap的所有特性,并且額外增加的按一定順序訪問的特性
存儲結構我們知道HashMap使用(數組 + 單鏈表 + 紅黑樹)的存儲結構,通過上面的繼承體系,我們知道LinkedHashMap繼承了Map,所以它的內部也有這三種結構,但是它還額外添加了一種“雙向鏈表”的結構存儲所有元素的順序。
添加刪除元素的時候需要同時維護在HashMap中的存儲,也要維護在LinkedList中的存儲,所以性能上來說會比HashMap稍慢。
源碼解析 屬性/** * 雙向鏈表頭節點 */ transient LinkedHashMap.Entryhead; /** * 雙向鏈表尾節點 */ transient LinkedHashMap.Entry tail; /** * 是否按訪問順序排序 */ final boolean accessOrder;
1.head
雙向鏈表的頭節點,舊數據存在頭節點。
2.tail
雙向鏈表的尾節點,新數據存在尾節點。
3.accessOrder
是否需要按訪問順序排序,如果為false則按插入順序存儲元素,如果是true則按訪問順序存儲元素。
// 位于LinkedHashMap中 static class Entryextends HashMap.Node { Entry before, after; Entry(int hash, K key, V value, Node next) { super(hash, key, value, next); } } // 位于HashMap中 static class Node implements Map.Entry { final int hash; final K key; V value; Node next; }
存儲節點,繼承自HashMap的Node類,next用于單鏈表存儲于桶中,before和after用于雙向鏈表存儲所有元素。
構造方法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(); accessOrder = false; putMapEntries(m, false); } public LinkedHashMap(int initialCapacity, float loadFactor, boolean accessOrder) { super(initialCapacity, loadFactor); this.accessOrder = accessOrder; }
前四個構造方法accessOrder都等于false,說明雙向鏈表是按插入順序存儲元素。
最后一個構造方法accessOrder從構造方法參數傳入,如果傳入true,則就實現了按訪問順序存儲元素,這也是實現LRU緩存策略的關鍵。
afterNodeInsertion(boolean evict)方法在節點插入之后做些什么,在HashMap中的putVal()方法中被調用,可以看到HashMap中這個方法的實現為空。
void afterNodeInsertion(boolean evict) { // possibly remove eldest LinkedHashMap.Entryfirst; if (evict && (first = head) != null && removeEldestEntry(first)) { K key = first.key; removeNode(hash(key), key, null, false, true); } } protected boolean removeEldestEntry(Map.Entry eldest) { return false; }
如果evict為true,且頭節點不為空,且確定移除最老的元素,那么就調用HashMap.removeNode()把頭節點移除(這里的頭節點是雙向鏈表的頭節點,而不是某個桶中的第一個元素);
HashMap.removeNode()從HashMap中把這個節點移除之后,會調用afterNodeRemoval()方法;
afterNodeRemoval()方法在LinkedHashMap中也有實現,用來在移除元素后修改雙向鏈表,見下文;
默認removeEldestEntry()方法返回false,也就是不刪除元素。
afterNodeAccess(Node在節點訪問之后被調用,主要在put()已經存在的元素或get()時被調用,如果accessOrder為true,調用這個方法把訪問到的節點移動到雙向鏈表的末尾。
void afterNodeAccess(Nodee) { // move node to last LinkedHashMap.Entry last; // 如果accessOrder為true,并且訪問的節點不是尾節點 if (accessOrder && (last = tail) != e) { LinkedHashMap.Entry p = (LinkedHashMap.Entry )e, b = p.before, a = p.after; // 把p節點從雙向鏈表中移除 p.after = null; if (b == null) head = a; else b.after = a; if (a != null) a.before = b; else last = b; // 把p節點放到雙向鏈表的末尾 if (last == null) head = p; else { p.before = last; last.after = p; } // 尾節點等于p tail = p; ++modCount; } }
如果accessOrder為true,并且訪問的節點不是尾節點;
從雙向鏈表中移除訪問的節點;
把訪問的節點加到雙向鏈表的末尾;(末尾為最新訪問的元素)
afterNodeRemoval(Node在節點被刪除之后調用的方法。
void afterNodeRemoval(Nodee) { // unlink LinkedHashMap.Entry p = (LinkedHashMap.Entry )e, b = p.before, a = p.after; // 把節點p從雙向鏈表中刪除。 p.before = p.after = null; if (b == null) head = a; else b.after = a; if (a == null) tail = b; else a.before = b; }
經典的把節點從雙向鏈表中刪除的方法。
get(Object key)方法獲取元素。
public V get(Object key) { Nodee; if ((e = getNode(hash(key), key)) == null) return null; if (accessOrder) afterNodeAccess(e); return e.value; }
如果查找到了元素,且accessOrder為true,則調用afterNodeAccess()方法把訪問的節點移到雙向鏈表的末尾。
總結LinkedHashMap繼承自HashMap,具有HashMap的所有特性;
LinkedHashMap內部維護了一個雙向鏈表存儲所有的元素;
如果accessOrder為false,則可以按插入元素的順序遍歷元素;
如果accessOrder為true,則可以按訪問元素的順序遍歷元素;
LinkedHashMap的實現非常精妙,很多方法都是在HashMap中留的鉤子(Hook),直接實現這些Hook就可以實現對應的功能了,并不需要再重寫put()等方法;
默認的LinkedHashMap并不會移除舊元素,如果需要移除舊元素,則需要重寫removeEldestEntry()方法設定移除策略;
LinkedHashMap可以用來實現LRU緩存淘汰策略;
LinkedHashMap 實現LRU緩存淘汰策略LRU,Least Recently Used,最近最少使用,也就是優先淘汰最近最少使用的元素。
如果使用LinkedHashMap,我們把accessOrder設置為true就差不多能實現這個策略了:
package com.coolcoding.code; import java.util.LinkedHashMap; import java.util.Map; public class LRUTest { public static void main(String[] args) { // 創建一個只有5個元素的緩存 LRUlru = new LRU<>(5, 0.75f); lru.put(1, 1); lru.put(2, 2); lru.put(3, 3); lru.put(4, 4); lru.put(5, 5); lru.put(6, 6); lru.put(7, 7); System.out.println(lru.get(4)); lru.put(6, 666); // 輸出: {3=3, 5=5, 7=7, 4=4, 6=666} // 可以看到最舊的元素被刪除了 // 且最近訪問的4被移到了后面 System.out.println(lru); } } class LRU extends LinkedHashMap { // 保存緩存的容量 private int capacity; public LRU(int capacity, float loadFactor) { super(capacity, loadFactor, true); this.capacity = capacity; } /** * 重寫removeEldestEntry()方法設置何時移除舊元素 * @param eldest * @return */ @Override protected boolean removeEldestEntry(Map.Entry eldest) { // 當元素個數大于了緩存的容量, 就移除元素 return size() > this.capacity; } }
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/76075.html
摘要:介紹底層是通過來實現的,它是一個有序的線程安全的集合。源碼分析它的源碼比較簡單,跟通過實現的基本是一致,只是多了一些取最近的元素的方法。 介紹 ConcurrentSkipListSet底層是通過ConcurrentNavigableMap來實現的,它是一個有序的線程安全的集合。 源碼分析 它的源碼比較簡單,跟通過Map實現的Set基本是一致,只是多了一些取最近的元素的方法。 // ...
摘要:存儲結構在中,的實現采用了數組鏈表紅黑樹的復雜結構,數組的一個元素又稱作桶。當一個鏈表的元素個數達到一定的數量且數組的長度達到一定的長度后,則把鏈表轉化為紅黑樹,從而提高效率。 簡介 HashMap采用key/value存儲結構,每個key對應唯一的value,查詢和修改的速度都很快,能達到O(1)的平均時間復雜度。它是非線程安全的,且不保證元素存儲的順序; 繼承體系 showImg(...
摘要:底層基于拉鏈式的散列結構,并在中引入紅黑樹優化過長鏈表的問題。在其之上,通過維護一條雙向鏈表,實現了散列數據結構的有序遍歷。 原文地址 LinkedHashMap LinkedHashMap繼承自HashMap實現了Map接口。基本實現同HashMap一樣,不同之處在于LinkedHashMap保證了迭代的有序性。其內部維護了一個雙向鏈表,解決了 HashMap不能隨時保持遍歷順序和插...
摘要:下面總結一下集合常用的三個子類吧無序,允許為,底層是散列表紅黑樹,非線程同步有序,不允許為,底層是紅黑樹非線程同步迭代有序,允許為,底層是雙向鏈表,非線程同步從結論而言我們就可以根據自己的實際情況來使用了。 前言 聲明,本文用的是jdk1.8 前面章節回顧: Collection總覽 List集合就這么簡單【源碼剖析】 Map集合、散列表、紅黑樹介紹 HashMap就是這么簡單【源碼...
package com.itheima.demo03.Map; import java.util.HashMap;import java.util.LinkedHashMap; /* java.util.LinkedHashMap entends HashMap Map 接口的哈希表和鏈接列表實現,具有可預知的迭代順序。 底層原理: 哈希表+鏈表(記錄元素的順序) */public cla...
閱讀 3153·2021-11-22 13:54
閱讀 3441·2021-11-15 11:37
閱讀 3606·2021-10-14 09:43
閱讀 3502·2021-09-09 11:52
閱讀 3599·2019-08-30 15:53
閱讀 2461·2019-08-30 13:50
閱讀 2060·2019-08-30 11:07
閱讀 891·2019-08-29 16:32