Java8 LinkedHashMap 源码阅读
如果你對 HashMap 的源碼有了解的話,只需要一圖就能知道 LinkedHashMap 的原理了,但是具體的實現(xiàn)細節(jié)還是需要去讀一下源碼。
一、LinkedHashMap 簡介
1.1 繼承結(jié)構(gòu)
從繼承結(jié)構(gòu)上來講 LinkedHashMap 繼承自 HashMap,LinkedHashMap 中沒有提供任何增刪改查的方法,而是直接復用了父類 HashMap 中的方法。
1.2 內(nèi)部數(shù)據(jù)結(jié)構(gòu)
LinkedHashMap 繼承自 HashMap,HashMap 底層存儲鍵值對的數(shù)據(jù)結(jié)構(gòu)是 Node 和 TreeNode,而 LinkedHashMap 存儲鍵值對的數(shù)據(jù)結(jié)構(gòu)是 Entry 和 TreeNode。
// Entry 繼承自 HashMap 中的 Node 類,用于鏈表存儲鍵值對static class Entry<K,V> extends HashMap.Node<K,V> {// 前置與后置節(jié)點,用于維護 LinkedHashMap 的鍵值對順序Entry<K,V> before, after;Entry(int hash, K key, V value, Node<K,V> next) {super(hash, key, value, next);}}Entry 用于存儲鏈表中的鍵值對,用 before 與 next 指針維護插入鍵值對的順序,那樹節(jié)點是怎么維護插入順序的呢?在 LinkedHashMap 中是沒有 TreeNode 類的,因為它復用了 HashMap 中的 TreeNode,下面我們來看一下 HashMap 中 TreeNode 的定義。
我們只看這一行代碼就行了,你會發(fā)現(xiàn) HashMap 中 TreeNode 類繼承了 LinkedHashMap 的 Entry,這樣一來就繼承了父類的前置與后置指針,也就能維護 LinkedHashMap 的插入順序了。
static final class TreeNode<K,V> extends LinkedHashMap.Entry<K,V>1.3 構(gòu)造函數(shù)
我們只看其中一個構(gòu)造函數(shù)
public LinkedHashMap(int initialCapacity,float loadFactor,boolean accessOrder) {// 調(diào)用 HashMap 的構(gòu)造函數(shù)super(initialCapacity, loadFactor);this.accessOrder = accessOrder;}LinkedHashMap 只有這一個構(gòu)造函數(shù)可以初始化 accessOrder,這個 accessOrder 屬性又有什么作用呢,下面來解釋一下,看了解釋大家可能還會有點迷惑,后面結(jié)合代碼大家就能理解這個字段的作用了。
/** 鍵值對迭代順序策略* true:access-order 訪問順序,使用迭代器遍歷時,get 的元素會被添加到最后* false:insertion-order 插入順序*/final boolean accessOrder;上面講了那么多都是為相關源碼分析作準備,下面我們就一起來看看吧。
二、添加鍵值對
上面我們也說了 LinkedHashMap 中沒有 put 方法,因為都是復用的 HashMap 的,如果你想具體了解 put 方法,可以查看 HashMap 擴容機制與線程安全分析 這篇文章。
想要把鍵值對放到 LinkedHashMap 中去,一定會先封裝成 Entry 或 TreeNode 節(jié)點,知道這個原理我們直接來看 newNode(以鏈表節(jié)點為例) 方法。
Node<K,V> newNode(int hash, K key, V value, Node<K,V> e) {// 初始化 LinkedHashMap 鍵值對 EntryLinkedHashMap.Entry<K,V> p =new LinkedHashMap.Entry<K,V>(hash, key, value, e);// 添加的鍵值對都會被添加到 LinkedHashMap 尾部,因此可以形成一個雙向鏈表linkNodeLast(p);return p;}在 newNode 方法初始化 Entry 節(jié)點時調(diào)用了一個 linkNodeLast 方法,我想看到這里大家應該這道其中的原理了。
private void linkNodeLast(LinkedHashMap.Entry<K,V> p) {// 獲取并記錄 tail 節(jié)點LinkedHashMap.Entry<K,V> last = tail;// 重置 tailtail = p;if (last == null)head = p;else {// 將節(jié)點連接起來,構(gòu)成雙向鏈表p.before = last;last.after = p;}}三、獲取鍵值對
LinkedHashMap 的 get 方法調(diào)用了 HashMap 的 getNode 方法,獲取到對應的節(jié)點后,返回對應的 value。
public V get(Object key) {Node<K,V> e;// 調(diào)用 hashMap 中的 getNode() 方法,根據(jù) key 的哈希值找到對應的桶位置,判斷節(jié)點后(鏈表、頭結(jié)點、樹節(jié)點)進行返回if ((e = getNode(hash(key), key)) == null)return null;// 如果 accessOrder 為 true,獲取元素后把當前鍵值對調(diào)整到尾部if (accessOrder)afterNodeAccess(e);return e.value;}下面我們來看一下 getNode 方法都做了什么。這個方法做的事情很簡答, 根據(jù) key 計算出對應的哈希值,根據(jù)哈希值計算出對應的桶位置,然后遍歷節(jié)點進行查找。
final Node<K,V> getNode(int hash, Object key) {Node<K,V>[] tab; Node<K,V> first, e; int n; K k;// 進行判斷并通過 tab[(n - 1) & hash] 計算當前 key 在哈希表中的位置if ((tab = table) != null && (n = tab.length) > 0 &&(first = tab[(n - 1) & hash]) != null) {// 如果是當前桶位置上的頭節(jié)點直接返回if (first.hash == hash && // always check first node((k = first.key) == key || (key != null && key.equals(k))))return first;// 如果頭節(jié)點不是要找的節(jié)點,判斷是樹節(jié)點還是鏈表節(jié)點后繼續(xù)查找if ((e = first.next) != null) {if (first instanceof TreeNode)return ((TreeNode<K,V>)first).getTreeNode(hash, key);// 鏈表、從頭節(jié)點開始遍歷查找do {if (e.hash == hash &&((k = e.key) == key || (key != null && key.equals(k))))return e;} while ((e = e.next) != null);}}return null;}上面的 get 方法中還有一個需要注意的地方,如果 accessOrdera 為 true 則調(diào)用 fterNodeAccess(e); 方法,這個方法又是干嘛用的呢,在最開始的時候我們說過,LinkedHashMap 支持兩種迭代策略,分別是訪問順序(默認)與插入順序,這個 fterNodeAccess(e); 方法就是用于支持訪問順序的。
如果 accessOrdera 為 true,那么在調(diào)用 get 方法時會將該鍵值對置為雙向鏈表的尾節(jié)點。
void afterNodeAccess(Node<K,V> e) { // move node to lastLinkedHashMap.Entry<K,V> last;// 判斷迭代策略,并且當前節(jié)點不是尾節(jié)點if (accessOrder && (last = tail) != e) {// 記錄當前節(jié)點,并獲取前后節(jié)點LinkedHashMap.Entry<K,V> p =(LinkedHashMap.Entry<K,V>)e, b = p.before, a = p.after;// 把當前節(jié)點的 after 節(jié)點置 nullp.after = null;// 如果當前節(jié)點是頭節(jié)點,把后一個節(jié)點置為頭節(jié)點if (b == null)head = a;// 把當前節(jié)點的前后節(jié)點相連elseb.after = a;if (a != null)a.before = b;elselast = b;if (last == null)head = p;// 把當前節(jié)點置為尾節(jié)點并記錄else {p.before = last;last.after = p;}tail = p;++modCount;}}四、HashMap 與 LinkedHashMap 對比
除了雙向鏈表外,HashMap 與 LinkedHashMap 還有什么區(qū)別嗎?
4.1 containsValue 方法對比
HashMap 中代碼如下,遍歷哈希表中的所有桶,然后遍歷桶位置上的所有節(jié)點。
public boolean containsValue(Object value) {Node<K,V>[] tab; V v;if ((tab = table) != null && size > 0) {// 遍歷哈希表for (int i = 0; i < tab.length; ++i) {// 遍歷當前桶上的所有節(jié)點for (Node<K,V> e = tab[i]; e != null; e = e.next) {if ((v = e.value) == value ||(value != null && value.equals(v)))return true;}}}return false;}LinkedHashMap 中代碼如下,從頭節(jié)點遍歷,一直遍歷到尾節(jié)點。
public boolean containsValue(Object value) {/*** 遍歷雙向鏈表,判斷 value,與 HashMap 完全不同*/for (LinkedHashMap.Entry<K,V> e = head; e != null; e = e.after) {V v = e.value;if (v == value || (value != null && value.equals(v)))return true;}return false;}4.2 nextNode 方法對比
HashMap 中的 nextNode() 方法,遍歷哈希表數(shù)組,接著遍歷桶位置上的所有節(jié)點。
final Node<K,V> nextNode() {// 記錄哈希表數(shù)組Node<K,V>[] t;Node<K,V> e = next;if (modCount != expectedModCount)throw new ConcurrentModificationException();if (e == null)throw new NoSuchElementException();// 過濾掉沒有鍵值對的桶位置if ((next = (current = e).next) == null && (t = table) != null) {do {} while (index < t.length && (next = t[index++]) == null);}// 下一個有鍵值對的桶(單個節(jié)點、樹節(jié)點或鏈表)return e;}LinkedHashMap 中的 nextNode() 方法,根據(jù)雙向鏈表的順序迭代鍵值對。
final LinkedHashMap.Entry<K,V> nextNode() {LinkedHashMap.Entry<K,V> e = next;if (modCount != expectedModCount)throw new ConcurrentModificationException();if (e == null)throw new NoSuchElementException();current = e;// next 為雙向鏈表的下一個節(jié)點next = e.after;return e;}jdk1.8 源碼閱讀:https://github.com/zchen96/jdk1.8-source-code-read
參考資料
搞懂 Java LinkedHashMap 源碼
總結(jié)
以上是生活随笔為你收集整理的Java8 LinkedHashMap 源码阅读的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 美国变态军人杀战友是什么电影
- 下一篇: Java8 ArrayBlockingQ