深入理解LinkedHashMap的實現原理(java8)
概述
在瞭解LinkedHashMap的實現原理之前,先看一下它的源碼介紹:
從註釋中,我們可以先了解到LinkedHashMap是通過哈希表和鏈表實現的,它通過維護一個鏈表來保證對哈希表迭代時的有序性,而這個有序是指鍵值對插入的順序。另外,當向哈希表中重複插入某個鍵的時候,不會影響到原來的有序性。
LinkedHashMap的實現主要分兩部分,一部分是哈希表,另外一部分是鏈表。哈希表部分繼承了HashMap,擁有了HashMap那一套高效的操作,所以我們要看的就是LinkedHashMap中鏈表的部分,瞭解它是如何來維護有序性的。
LinkedHashMap 的大致實現如下圖所示,當然鏈表和哈希表中相同的鍵值對都是指向同一個對象,這裏把它們分開來畫只是爲了呈現出比較清晰的結構。
LinkedHashMap的聲明:
LinkedHashMap 是繼承自 HashMap 的,所以它已經從 HashMap 那裏繼承了與哈希表相關的操作了
LinkedHashMap的屬性
關於LinkedHahMap屬性的源碼:
static class Entry<K,V> extends HashMap.Node<K,V> {
Entry<K,V> before, after;
Entry(int hash, K key, V value, Node<K,V> next) {
super(hash, key, value, next);
}
}
private static final long serialVersionUID = 3801124242820219131L;
/**
* The head (eldest) of the doubly linked list.
*/
transient LinkedHashMap.Entry<K,V> head;
/**
* The tail (youngest) of the doubly linked list.
*/
transient LinkedHashMap.Entry<K,V> tail;
/**
* The iteration ordering method for this linked hash map: {@code true}
* for access-order, {@code false} for insertion-order.
*
* @serial
*/
final boolean accessOrder;
可以發現:LinkedHashMap的鏈表節點
繼承了HashMap的節點
,而且每個節點都包含了前指針和後指針,所以這裏可以看出它是一個雙向鏈表.
transient LinkedHashMap.Entry<K,V> head;
: 頭指針
transient LinkedHashMap.Entry<K,V> tail;
: 尾指針
LinkedhashMap的一些方法
其實,在HashMap源碼中有這三個空的方法,其實這三個方法表示的是在訪問、插入、刪除某個節點之後,進行一些處理,它們在LinkedHashMap都有各自的實現。LinkedHashMap正是通過重寫這三個方法來保證鏈表的插入、刪除的有序性。
在LinkedHashMAP中這三者方法的介紹
afterNodeAccess()
:移動節點到尾部
void afterNodeAccess(Node<K,V> e) { // move node to last
LinkedHashMap.Entry<K,V> last;
//當accessOrder的值爲true,且e不是尾節點
if (accessOrder && (last = tail) != e) {
LinkedHashMap.Entry<K,V> p =
(LinkedHashMap.Entry<K,V>)e, b = p.before, a = p.after;
p.after = null;
if (b == null)
head = a;
else
b.after = a;
if (a != null)
a.before = b;
else
last = b;
if (last == null)
head = p;
else {
p.before = last;
last.after = p;
}
tail = p;
++modCount;
}
}
這段代碼的意思簡潔明瞭,就是把當前節點e移至鏈表的尾部。因爲使用的是雙向鏈表,所以在尾部插入可以以O(1)的時間複雜度來完成。並且只有當accessOrder設置爲true時,纔會執行這個操作。在HashMap的putVal方法中,就調用了這個方法
afterNodeInsertion():
void afterNodeInsertion(boolean evict) { // possibly remove eldest
LinkedHashMap.Entry<K,V> first;
if (evict && (first = head) != null && removeEldestEntry(first)) {
K key = first.key;
removeNode(hash(key), key, null, false, true);
}
}
afterNodeInsertion方法是在哈希表中插入了一個新節點時調用的,它會把鏈表的頭節點刪除掉,刪除的方式是通過調用HashMap的removeNode方法。
afterNodeRemoval()
void afterNodeRemoval(Node<K,V> e) { // unlink
LinkedHashMap.Entry<K,V> p =
(LinkedHashMap.Entry<K,V>)e, b = p.before, a = p.after;
p.before = p.after = null;
if (b == null)
head = a;
else
b.after = a;
if (a == null)
tail = b;
else
a.before = b;
}
這個方法是當HashMap刪除一個鍵值對時調用的,它會把在HashMap中刪除的那個鍵值對一併從鏈表中刪除,保證了哈希表和鏈表的一致性。
get()
public V get(Object key) {
Node<K,V> e;
if ((e = getNode(hash(key), key)) == null)
return null;
if (accessOrder)
afterNodeAccess(e);
return e.value;
}
get()方法,它調用的是HashMap的getNode方法來獲取結果的。