摘要:關(guān)于的源碼分析,本文并不打算展開講了。大家可以參考我之前的一篇文章源碼詳細(xì)分析。在刪除節(jié)點(diǎn)時(shí),父類的刪除邏輯并不會(huì)修復(fù)所維護(hù)的雙向鏈表,這不是它的職責(zé)。在節(jié)分析鏈表建立過程時(shí),我故意忽略了部分源碼分析。
1. 概述
LinkedHashMap 繼承自 HashMap,在 HashMap 基礎(chǔ)上,通過維護(hù)一條雙向鏈表,解決了 HashMap 不能隨時(shí)保持遍歷順序和插入順序一致的問題。除此之外,LinkedHashMap 對(duì)訪問順序也提供了相關(guān)支持。在一些場(chǎng)景下,該特性很有用,比如緩存。在實(shí)現(xiàn)上,LinkedHashMap 很多方法直接繼承自 HashMap,僅為維護(hù)雙向鏈表覆寫了部分方法。所以,要看懂 LinkedHashMap 的源碼,需要先看懂 HashMap 的源碼。關(guān)于 HashMap 的源碼分析,本文并不打算展開講了。大家可以參考我之前的一篇文章“HashMap 源碼詳細(xì)分析(JDK1.8)”。在那篇文章中,我配了十多張圖幫助大家學(xué)習(xí) HashMap 源碼。
本篇文章的結(jié)構(gòu)與我之前兩篇關(guān)于 Java 集合類(集合框架)的源碼分析文章不同,本文將不再分析集合類的基本操作(查找、遍歷、插入、刪除),而是把重點(diǎn)放在雙向鏈表的維護(hù)上。包括鏈表的建立過程,刪除節(jié)點(diǎn)的過程,以及訪問順序維護(hù)的過程等。好了,接下里開始分析吧。
2. 原理上一章說了 LinkedHashMap 繼承自 HashMap,所以它的底層仍然是基于拉鏈?zhǔn)缴⒘薪Y(jié)構(gòu)。該結(jié)構(gòu)由數(shù)組和鏈表或紅黑樹組成,結(jié)構(gòu)示意圖大致如下:
LinkedHashMap 在上面結(jié)構(gòu)的基礎(chǔ)上,增加了一條雙向鏈表,使得上面的結(jié)構(gòu)可以保持鍵值對(duì)的插入順序。同時(shí)通過對(duì)鏈表進(jìn)行相應(yīng)的操作,實(shí)現(xiàn)了訪問順序相關(guān)邏輯。其結(jié)構(gòu)可能如下圖:
上圖中,淡藍(lán)色的箭頭表示前驅(qū)引用,紅色箭頭表示后繼引用。每當(dāng)有新鍵值對(duì)節(jié)點(diǎn)插入,新節(jié)點(diǎn)最終會(huì)接在 tail 引用指向的節(jié)點(diǎn)后面。而 tail 引用則會(huì)移動(dòng)到新的節(jié)點(diǎn)上,這樣一個(gè)雙向鏈表就建立起來了。
上面的結(jié)構(gòu)并不是很難理解,雖然引入了紅黑樹,導(dǎo)致結(jié)構(gòu)看起來略為復(fù)雜了一些。但大家完全可以忽略紅黑樹,而只關(guān)注鏈表結(jié)構(gòu)本身。好了,接下來進(jìn)入細(xì)節(jié)分析吧。
3. 源碼分析 3.1 Entry 的繼承體系在對(duì)核心內(nèi)容展開分析之前,這里先插隊(duì)分析一下鍵值對(duì)節(jié)點(diǎn)的繼承體系。先來看看繼承體系結(jié)構(gòu)圖:
上面的繼承體系乍一看還是有點(diǎn)復(fù)雜的,同時(shí)也有點(diǎn)讓人迷惑。HashMap 的內(nèi)部類 TreeNode 不繼承它的了一個(gè)內(nèi)部類 Node,卻繼承自 Node 的子類 LinkedHashMap 內(nèi)部類 Entry。這里這樣做是有一定原因的,這里先不說。先來簡(jiǎn)單說明一下上面的繼承體系。LinkedHashMap 內(nèi)部類 Entry 繼承自 HashMap 內(nèi)部類 Node,并新增了兩個(gè)引用,分別是 before 和 after。這兩個(gè)引用的用途不難理解,也就是用于維護(hù)雙向鏈表。同時(shí),TreeNode 繼承 LinkedHashMap 的內(nèi)部類 Entry 后,就具備了和其他 Entry 一起組成鏈表的能力。但是這里需要大家考慮一個(gè)問題。當(dāng)我們使用 HashMap 時(shí),TreeNode 并不需要具備組成鏈表能力。如果繼承 LinkedHashMap 內(nèi)部類 Entry ,TreeNode 就多了兩個(gè)用不到的引用,這樣做不是會(huì)浪費(fèi)空間嗎?簡(jiǎn)單說明一下這個(gè)問題(水平有限,不保證完全正確),這里這么做確實(shí)會(huì)浪費(fèi)空間,但與 TreeNode 通過繼承獲取的組成鏈表的能力相比,這點(diǎn)浪費(fèi)是值得的。在 HashMap 的設(shè)計(jì)思路注釋中,有這樣一段話:
Because TreeNodes are about twice the size of regular nodes, we
use them only when bins contain enough nodes to warrant use
(see TREEIFY_THRESHOLD). And when they become too small (due to
removal or resizing) they are converted back to plain bins. In
usages with well-distributed user hashCodes, tree bins are
rarely used.
大致的意思是 TreeNode 對(duì)象的大小約是普通 Node 對(duì)象的2倍,我們僅在桶(bin)中包含足夠多的節(jié)點(diǎn)時(shí)再使用。當(dāng)桶中的節(jié)點(diǎn)數(shù)量變少時(shí)(取決于刪除和擴(kuò)容),TreeNode 會(huì)被轉(zhuǎn)成 Node。當(dāng)用戶實(shí)現(xiàn)的 hashCode 方法具有良好分布性時(shí),樹類型的桶將會(huì)很少被使用。
通過上面的注釋,我們可以了解到。一般情況下,只要 hashCode 的實(shí)現(xiàn)不糟糕,Node 組成的鏈表很少會(huì)被轉(zhuǎn)成由 TreeNode 組成的紅黑樹。也就是說 TreeNode 使用的并不多,浪費(fèi)那點(diǎn)空間是可接受的。假如 TreeNode 機(jī)制繼承自 Node 類,那么它要想具備組成鏈表的能力,就需要 Node 去繼承 LinkedHashMap 的內(nèi)部類 Entry。這個(gè)時(shí)候就得不償失了,浪費(fèi)很多空間去獲取不一定用得到的能力。
說到這里,大家應(yīng)該能明白節(jié)點(diǎn)類型的繼承體系了。這里多帶帶拿出來說一下,為下面的分析做鋪墊。敘述略為啰嗦,見諒。
3.1 鏈表的建立過程鏈表的建立過程是在插入鍵值對(duì)節(jié)點(diǎn)時(shí)開始的,初始情況下,讓 LinkedHashMap 的 head 和 tail 引用同時(shí)指向新節(jié)點(diǎn),鏈表就算建立起來了。隨后不斷有新節(jié)點(diǎn)插入,通過將新節(jié)點(diǎn)接在 tail 引用指向節(jié)點(diǎn)的后面,即可實(shí)現(xiàn)鏈表的更新。
Map 類型的集合類是通過 put(K,V) 方法插入鍵值對(duì),LinkedHashMap 本身并沒有覆寫父類的 put 方法,而是直接使用了父類的實(shí)現(xiàn)。但在 HashMap 中,put 方法插入的是 HashMap 內(nèi)部類 Node 類型的節(jié)點(diǎn),該類型的節(jié)點(diǎn)并不具備與 LinkedHashMap 內(nèi)部類 Entry 及其子類型節(jié)點(diǎn)組成鏈表的能力。那么,LinkedHashMap 是怎樣建立鏈表的呢?在展開說明之前,我們先看一下 LinkedHashMap 插入操作相關(guān)的代碼:
// HashMap 中實(shí)現(xiàn) public V put(K key, V value) { return putVal(hash(key), key, value, false, true); } // HashMap 中實(shí)現(xiàn) final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) { Node[] tab; Node p; int n, i; if ((tab = table) == null || (n = tab.length) == 0) {...} // 通過節(jié)點(diǎn) hash 定位節(jié)點(diǎn)所在的桶位置,并檢測(cè)桶中是否包含節(jié)點(diǎn)引用 if ((p = tab[i = (n - 1) & hash]) == null) {...} else { Node e; K k; if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k)))) e = p; else if (p instanceof TreeNode) {...} else { // 遍歷鏈表,并統(tǒng)計(jì)鏈表長(zhǎng)度 for (int binCount = 0; ; ++binCount) { // 未在單鏈表中找到要插入的節(jié)點(diǎn),將新節(jié)點(diǎn)接在單鏈表的后面 if ((e = p.next) == null) { p.next = newNode(hash, key, value, null); if (binCount >= TREEIFY_THRESHOLD - 1) {...} break; } // 插入的節(jié)點(diǎn)已經(jīng)存在于單鏈表中 if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) break; p = e; } } if (e != null) { // existing mapping for key V oldValue = e.value; if (!onlyIfAbsent || oldValue == null) {...} afterNodeAccess(e); // 回調(diào)方法,后續(xù)說明 return oldValue; } } ++modCount; if (++size > threshold) {...} afterNodeInsertion(evict); // 回調(diào)方法,后續(xù)說明 return null; } // HashMap 中實(shí)現(xiàn) Node newNode(int hash, K key, V value, Node next) { return new Node<>(hash, key, value, next); } // LinkedHashMap 中覆寫 Node newNode(int hash, K key, V value, Node e) { LinkedHashMap.Entry p = new LinkedHashMap.Entry (hash, key, value, e); // 將 Entry 接在雙向鏈表的尾部 linkNodeLast(p); return p; } // LinkedHashMap 中實(shí)現(xiàn) private void linkNodeLast(LinkedHashMap.Entry p) { LinkedHashMap.Entry last = tail; tail = p; // last 為 null,表明鏈表還未建立 if (last == null) head = p; else { // 將新節(jié)點(diǎn) p 接在鏈表尾部 p.before = last; last.after = p; } }
上面就是 LinkedHashMap 插入相關(guān)的源碼,這里省略了部分非關(guān)鍵的代碼。我根據(jù)上面的代碼,可以知道 LinkedHashMap 插入操作的調(diào)用過程。如下:
我把 newNode 方法紅色背景標(biāo)注了出來,這一步比較關(guān)鍵。LinkedHashMap 覆寫了該方法。在這個(gè)方法中,LinkedHashMap 創(chuàng)建了 Entry,并通過 linkNodeLast 方法將 Entry 接在雙向鏈表的尾部,實(shí)現(xiàn)了雙向鏈表的建立。雙向鏈表建立之后,我們就可以按照插入順序去遍歷 LinkedHashMap,大家可以自己寫點(diǎn)測(cè)試代碼驗(yàn)證一下插入順序。
以上就是 LinkedHashMap 維護(hù)插入順序的相關(guān)分析。本節(jié)的最后,再額外補(bǔ)充一些東西。大家如果仔細(xì)看上面的代碼的話,會(huì)發(fā)現(xiàn)有兩個(gè)以after開頭方法,在上文中沒有被提及。在 JDK 1.8 HashMap 的源碼中,相關(guān)的方法有3個(gè):
// Callbacks to allow LinkedHashMap post-actions void afterNodeAccess(Nodep) { } void afterNodeInsertion(boolean evict) { } void afterNodeRemoval(Node p) { }
根據(jù)這三個(gè)方法的注釋可以看出,這些方法的用途是在增刪查等操作后,通過回調(diào)的方式,讓 LinkedHashMap 有機(jī)會(huì)做一些后置操作。上述三個(gè)方法的具體實(shí)現(xiàn)在 LinkedHashMap 中,本節(jié)先不分析這些實(shí)現(xiàn),相關(guān)分析會(huì)在后續(xù)章節(jié)中進(jìn)行。
3.2 鏈表節(jié)點(diǎn)的刪除過程與插入操作一樣,LinkedHashMap 刪除操作相關(guān)的代碼也是直接用父類的實(shí)現(xiàn)。在刪除節(jié)點(diǎn)時(shí),父類的刪除邏輯并不會(huì)修復(fù) LinkedHashMap 所維護(hù)的雙向鏈表,這不是它的職責(zé)。那么刪除及節(jié)點(diǎn)后,被刪除的節(jié)點(diǎn)該如何從雙鏈表中移除呢?當(dāng)然,辦法還算是有的。上一節(jié)最后提到 HashMap 中三個(gè)回調(diào)方法運(yùn)行 LinkedHashMap 對(duì)一些操作做出響應(yīng)。所以,在刪除及節(jié)點(diǎn)后,回調(diào)方法 afterNodeRemoval 會(huì)被調(diào)用。LinkedHashMap 覆寫該方法,并在該方法中完成了移除被刪除節(jié)點(diǎn)的操作。相關(guān)源碼如下:
// HashMap 中實(shí)現(xiàn) public V remove(Object key) { Nodee; return (e = removeNode(hash(key), key, null, false, true)) == null ? null : e.value; } // HashMap 中實(shí)現(xiàn) final Node removeNode(int hash, Object key, Object value, boolean matchValue, boolean movable) { Node [] tab; Node p; int n, index; if ((tab = table) != null && (n = tab.length) > 0 && (p = tab[index = (n - 1) & hash]) != null) { Node node = null, e; K k; V v; if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k)))) node = p; else if ((e = p.next) != null) { if (p instanceof TreeNode) {...} else { // 遍歷單鏈表,尋找要?jiǎng)h除的節(jié)點(diǎn),并賦值給 node 變量 do { if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) { node = e; break; } p = e; } while ((e = e.next) != null); } } if (node != null && (!matchValue || (v = node.value) == value || (value != null && value.equals(v)))) { if (node instanceof TreeNode) {...} // 將要?jiǎng)h除的節(jié)點(diǎn)從單鏈表中移除 else if (node == p) tab[index] = node.next; else p.next = node.next; ++modCount; --size; afterNodeRemoval(node); // 調(diào)用刪除回調(diào)方法進(jìn)行后續(xù)操作 return node; } } return null; } // LinkedHashMap 中覆寫 void afterNodeRemoval(Node e) { // unlink LinkedHashMap.Entry p = (LinkedHashMap.Entry )e, b = p.before, a = p.after; // 將 p 節(jié)點(diǎn)的前驅(qū)后后繼引用置空 p.before = p.after = null; // b 為 null,表明 p 是頭節(jié)點(diǎn) if (b == null) head = a; else b.after = a; // a 為 null,表明 p 是尾節(jié)點(diǎn) if (a == null) tail = b; else a.before = b; }
刪除的過程并不復(fù)雜,上面這么多代碼其實(shí)就做了三件事:
根據(jù) hash 定位到桶位置
遍歷鏈表或調(diào)用紅黑樹相關(guān)的刪除方法
從 LinkedHashMap 維護(hù)的雙鏈表中移除要?jiǎng)h除的節(jié)點(diǎn)
舉個(gè)例子說明一下,假如我們要?jiǎng)h除下圖鍵值為 3 的節(jié)點(diǎn)。
根據(jù) hash 定位到該節(jié)點(diǎn)屬于3號(hào)桶,然后在對(duì)3號(hào)桶保存的單鏈表進(jìn)行遍歷。找到要?jiǎng)h除的節(jié)點(diǎn)后,先從單鏈表中移除該節(jié)點(diǎn)。如下:
然后再雙向鏈表中移除該節(jié)點(diǎn):
刪除及相關(guān)修復(fù)過程并不復(fù)雜,結(jié)合上面的圖片,大家應(yīng)該很容易就能理解,這里就不多說了。
3.3 訪問順序的維護(hù)過程前面說了插入順序的實(shí)現(xiàn),本節(jié)來講講訪問順序。默認(rèn)情況下,LinkedHashMap 是按插入順序維護(hù)鏈表。不過我們可以在初始化 LinkedHashMap,指定 accessOrder 參數(shù)為 true,即可讓它按訪問順序維護(hù)鏈表。訪問順序的原理上并不復(fù)雜,當(dāng)我們調(diào)用get/getOrDefault/replace等方法時(shí),只需要將這些方法訪問的節(jié)點(diǎn)移動(dòng)到鏈表的尾部即可。相應(yīng)的源碼如下:
// LinkedHashMap 中覆寫 public V get(Object key) { Nodee; if ((e = getNode(hash(key), key)) == null) return null; // 如果 accessOrder 為 true,則調(diào)用 afterNodeAccess 將被訪問節(jié)點(diǎn)移動(dòng)到鏈表最后 if (accessOrder) afterNodeAccess(e); return e.value; } // LinkedHashMap 中覆寫 void afterNodeAccess(Node e) { // move node to last LinkedHashMap.Entry last; if (accessOrder && (last = tail) != e) { LinkedHashMap.Entry p = (LinkedHashMap.Entry )e, b = p.before, a = p.after; p.after = null; // 如果 b 為 null,表明 p 為頭節(jié)點(diǎn) if (b == null) head = a; else b.after = a; if (a != null) a.before = b; /* * 這里存疑,父條件分支已經(jīng)確保節(jié)點(diǎn) e 不會(huì)是尾節(jié)點(diǎn), * 那么 e.after 必然不會(huì)為 null,不知道 else 分支有什么作用 */ else last = b; if (last == null) head = p; else { // 將 p 接在鏈表的最后 p.before = last; last.after = p; } tail = p; ++modCount; } }
上面就是訪問順序的實(shí)現(xiàn)代碼,并不復(fù)雜。下面舉例演示一下,幫助大家理解。假設(shè)我們?cè)L問下圖鍵值為3的節(jié)點(diǎn),訪問前結(jié)構(gòu)為:
訪問后,鍵值為3的節(jié)點(diǎn)將會(huì)被移動(dòng)到雙向鏈表的最后位置,其前驅(qū)和后繼也會(huì)跟著更新。訪問后的結(jié)構(gòu)如下:
3.4 基于 LinkedHashMap 實(shí)現(xiàn)緩存前面介紹了 LinkedHashMap 是如何維護(hù)插入和訪問順序的,大家對(duì) LinkedHashMap 的原理應(yīng)該有了一定的認(rèn)識(shí)。本節(jié)我們來寫一些代碼實(shí)踐一下,這里通過繼承 LinkedHashMap 實(shí)現(xiàn)了一個(gè)簡(jiǎn)單的 LRU 策略的緩存。在寫代碼之前,先介紹一下前置知識(shí)。
在3.1節(jié)分析鏈表建立過程時(shí),我故意忽略了部分源碼分析。本節(jié)就把忽略的部分補(bǔ)上,先看源碼吧:
void afterNodeInsertion(boolean evict) { // possibly remove eldest LinkedHashMap.Entryfirst; // 根據(jù)條件判斷是否移除最近最少被訪問的節(jié)點(diǎn) if (evict && (first = head) != null && removeEldestEntry(first)) { K key = first.key; removeNode(hash(key), key, null, false, true); } } // 移除最近最少被訪問條件之一,通過覆蓋此方法可實(shí)現(xiàn)不同策略的緩存 protected boolean removeEldestEntry(Map.Entry eldest) { return false; }
上面的源碼的核心邏輯在一般情況下都不會(huì)被執(zhí)行,所以之前并沒有進(jìn)行分析。上面的代碼做的事情比較簡(jiǎn)單,就是通過一些條件,判斷是否移除最近最少被訪問的節(jié)點(diǎn)。看到這里,大家應(yīng)該知道上面兩個(gè)方法的用途了。當(dāng)我們基于 LinkedHashMap 實(shí)現(xiàn)緩存時(shí),通過覆寫removeEldestEntry方法可以實(shí)現(xiàn)自定義策略的 LRU 緩存。比如我們可以根據(jù)節(jié)點(diǎn)數(shù)量判斷是否移除最近最少被訪問的節(jié)點(diǎn),或者根據(jù)節(jié)點(diǎn)的存活時(shí)間判斷是否移除該節(jié)點(diǎn)等。本節(jié)所實(shí)現(xiàn)的緩存是基于判斷節(jié)點(diǎn)數(shù)量是否超限的策略。在構(gòu)造緩存對(duì)象時(shí),傳入最大節(jié)點(diǎn)數(shù)。當(dāng)插入的節(jié)點(diǎn)數(shù)超過最大節(jié)點(diǎn)數(shù)時(shí),移除最近最少被訪問的節(jié)點(diǎn)。實(shí)現(xiàn)代碼如下:
public class SimpleCacheextends LinkedHashMap { private static final int MAX_NODE_NUM = 100; private int limit; public SimpleCache() { this(MAX_NODE_NUM); } public SimpleCache(int limit) { super(limit, 0.75f, true); this.limit = limit; } public V save(K key, V val) { return put(key, val); } public V getOne(K key) { return get(key); } public boolean exists(K key) { return containsKey(key); } /** * 判斷節(jié)點(diǎn)數(shù)是否超限 * @param eldest * @return 超限返回 true,否則返回 false */ @Override protected boolean removeEldestEntry(Map.Entry eldest) { return size() > limit; } }
測(cè)試代碼如下:
public class SimpleCacheTest { @Test public void test() throws Exception { SimpleCachecache = new SimpleCache<>(3); for (int i = 0; i < 10; i++) { cache.save(i, i * i); } System.out.println("插入10個(gè)鍵值對(duì)后,緩存內(nèi)容:"); System.out.println(cache + " "); System.out.println("訪問鍵值為7的節(jié)點(diǎn)后,緩存內(nèi)容:"); cache.getOne(7); System.out.println(cache + " "); System.out.println("插入鍵值為1的鍵值對(duì)后,緩存內(nèi)容:"); cache.save(1, 1); System.out.println(cache); } }
測(cè)試結(jié)果如下:
在測(cè)試代碼中,設(shè)定緩存大小為3。在向緩存中插入10個(gè)鍵值對(duì)后,只有最后3個(gè)被保存下來了,其他的都被移除了。然后通過訪問鍵值為7的節(jié)點(diǎn),使得該節(jié)點(diǎn)被移到雙向鏈表的最后位置。當(dāng)我們?cè)俅尾迦胍粋€(gè)鍵值對(duì)時(shí),鍵值為7的節(jié)點(diǎn)就不會(huì)被移除。
本節(jié)作為對(duì)前面內(nèi)的補(bǔ)充,簡(jiǎn)單介紹了 LinkedHashMap 在其他方面的應(yīng)用。本節(jié)內(nèi)容及相關(guān)代碼并不難理解,這里就不在贅述了。
4. 總結(jié)本文從 LinkedHashMap 維護(hù)雙向鏈表的角度對(duì) LinkedHashMap 的源碼進(jìn)行了分析,并在文章的結(jié)尾基于 LinkedHashMap 實(shí)現(xiàn)了一個(gè)簡(jiǎn)單的 Cache。在日常開發(fā)中,LinkedHashMap 的使用頻率雖不及 HashMap,但它也個(gè)重要的實(shí)現(xiàn)。在 Java 集合框架中,HashMap、LinkedHashMap 和 TreeMap 三個(gè)映射類基于不同的數(shù)據(jù)結(jié)構(gòu),并實(shí)現(xiàn)了不同的功能。HashMap 底層基于拉鏈?zhǔn)降纳⒘薪Y(jié)構(gòu),并在 JDK 1.8 中引入紅黑樹優(yōu)化過長(zhǎng)鏈表的問題。基于這樣結(jié)構(gòu),HashMap 可提供高效的增刪改查操作。LinkedHashMap 在其之上,通過維護(hù)一條雙向鏈表,實(shí)現(xiàn)了散列數(shù)據(jù)結(jié)構(gòu)的有序遍歷。TreeMap 底層基于紅黑樹實(shí)現(xiàn),利用紅黑樹的性質(zhì),實(shí)現(xiàn)了鍵值對(duì)排序功能。我在前面幾篇文章中,對(duì) HashMap 和 TreeMap 以及他們均使用到的紅黑樹進(jìn)行了詳細(xì)的分析,有興趣的朋友可以去看看。
到此,本篇文章就寫完了,感謝大家的閱讀!
附錄:映射類文章列表紅黑樹詳細(xì)分析
TreeMap源碼分析
HashMap 源碼詳細(xì)分析(JDK1.8)
本文在知識(shí)共享許可協(xié)議 4.0 下發(fā)布,轉(zhuǎn)載請(qǐng)注明出處
作者:coolblog
本文同步發(fā)布在我的個(gè)人博客:http://www.coolblog.xyz
本作品采用知識(shí)共享署名-非商業(yè)性使用-禁止演繹 4.0 國(guó)際許可協(xié)議進(jìn)行許可。
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://m.specialneedsforspecialkids.com/yun/68331.html
摘要:若遇到哈希沖突,則將沖突的值加到鏈表中即可。之后相比于之前的版本,之后在解決哈希沖突時(shí)有了較大的變化,當(dāng)鏈表長(zhǎng)度大于閾值默認(rèn)為時(shí),將鏈表轉(zhuǎn)化為紅黑樹,以減少搜索時(shí)間。有序,唯一紅黑樹自平衡的排序二叉樹。 本文是最最最常見Java面試題總結(jié)系列第三周的文章。主要內(nèi)容: Arraylist 與 LinkedList 異同 ArrayList 與 Vector 區(qū)別 HashMap的底層...
摘要:三系列用于保存鍵值對(duì),無論是,還是已棄用的或者線程安全的等,都是基于紅黑樹。是完全基于紅黑樹的,并在此基礎(chǔ)上實(shí)現(xiàn)了接口。可以看到,只有紅黑樹,且紅黑樹是通過內(nèi)部類來實(shí)現(xiàn)的。 JDK容器 前言 閱讀JDK源碼有段時(shí)間了,準(zhǔn)備以博客的形式記錄下來,也方便復(fù)習(xí)時(shí)查閱,本文參考JDK1.8源碼。 一、Collection Collection是所有容器的基類,定義了一些基礎(chǔ)方法。List、Se...
摘要:如今行至于此,當(dāng)觀賞一方。由于所返回的無執(zhí)行意義。源碼閱讀總體門檻相對(duì)而言比,畢竟大多數(shù)底層都由實(shí)現(xiàn)了。比心可通過這篇文章理解創(chuàng)建一個(gè)實(shí)例過程圖工作原理往期線路回顧源碼一帶一路系列之源碼一帶一路系列之源碼一帶一路系列之 本文以jdk1.8中LinkedHashMap.afterNodeAccess()方法為切入點(diǎn),分析其中難理解、有價(jià)值的源碼片段(類似源碼查看是ctrl+鼠標(biāo)左鍵的過程...
摘要:當(dāng)鏈表長(zhǎng)度即將超過閥值,會(huì)把鏈表轉(zhuǎn)化為紅黑樹。然后再判斷是鏈表還是紅黑樹如果值相同,并且相同表示數(shù)組中第一個(gè)元素即為相同的將數(shù)組中第一個(gè)元素賦值給如果當(dāng)前元素類型為表示為紅黑樹,返回待存放的。 前提:學(xué)習(xí)HashMap的底層代碼之前,首先要對(duì)數(shù)據(jù)結(jié)構(gòu)要個(gè)大致的了解。其中重點(diǎn)了解數(shù)組,鏈表,樹的概念和用法。 一.圖示分析HashMap的結(jié)構(gòu) (1)圖示為JDK1.8之前的HashMap結(jié)...
閱讀 1771·2023-04-26 00:20
閱讀 1819·2021-11-08 13:21
閱讀 2010·2021-09-10 10:51
閱讀 1577·2021-09-10 10:50
閱讀 3310·2019-08-30 15:54
閱讀 2142·2019-08-30 14:22
閱讀 1438·2019-08-29 16:10
閱讀 3098·2019-08-26 11:50