jdk原始碼分析之LinkedHashMap
基本原理
LinkedHashMap繼承自HashMap,因此具有HashMap的所有特性。在HashMap的基礎上,保持了key的插入順序或者訪問順序。實現方法就是用雙向迴圈連結串列將所有的entry連結起來,讀取資料的時候直接讀取此雙向迴圈連結串列的資料即可。
public class LinkedHashMap<K,V> extends HashMap<K,V> implements Map<K,V>
/**
* The head of the doubly linked list.
*/
private transient Entry<K,V> header;
/**
* The iteration ordering method for this linked hash map:
* true for access-order,false for insertion-order.
*/
private final boolean accessOrder;
header是雙向迴圈連結串列的頭結點,不儲存資料,用於定位頭尾節點
header.after指向連結串列的第一個節點
header.before指向連結串列的最後一個節點
accessOrder是一個標誌位,true的話表示按照訪問順序訪問連結串列,可據此構建LRU快取,fasle的話表示按照插入順序訪問連結串列
雙向迴圈連結串列的Entry節點
LinkedHashMap的Entry繼承自HashMap的Entry
private static class Entry<K,V> extends HashMap.Entry<K,V>
並新增了指向前序節點的前序指標和指向後繼節點的後繼指標
// These fields comprise the doubly linked list used for iteration.
Entry<K,V> before, after;
因此LinkedHashMap的Entry節點具有三個指標域,next指標維護Hash桶中衝突key的連結串列,before和after維護雙向迴圈連結串列
為了維護雙向迴圈連結串列,Entry新增加了4個方法
刪除節點remove
/**
* Removes this entry from the linked list.
*/
private void remove() {
before.after = after;
after.before = before;
}
充分體現了連結串列這種資料結構刪除中間節點的方便之處,僅僅修改指標的指向即可。
如果要刪除當前節點,就把當前節點的前序節點的後繼指標指向當前節點的後繼節點,當前節點的後繼節點的前序指標指向當前節點的前序節點即可,這樣當前節點就從連結串列中脫離開了,斷開的節點也得到重新連結。
新增節點
/**
* Inserts this entry before the specified existing entry in the list.
*/
private void addBefore(Entry<K,V> existingEntry) {
after = existingEntry;
before = existingEntry.before;
before.after = this;
after.before = this;
}
新增節點同樣是指標操作,非常高效方便
記錄訪問recordAccess
/**
* This method is invoked by the superclass whenever the value
* of a pre-existing entry is read by Map.get or modified by Map.set.
* If the enclosing Map is access-ordered, it moves the entry
* to the end of the list; otherwise, it does nothing.
*/
void recordAccess(HashMap<K,V> m) {
LinkedHashMap<K,V> lm = (LinkedHashMap<K,V>)m;
if (lm.accessOrder) {
lm.modCount++;
remove();
addBefore(lm.header);
}
}
當LinkedHashMap的標誌位accessOrder為true時,標誌著要採用訪問順序訪問連結串列。
remove(); addBefore(lm.header);
最新訪問的節點先從原來的位置刪除,然後重現新增到連結串列的末尾,這樣最近最少訪問的節點就被挪到了連結串列的前端。
LinkedHashMap新增資料
LinkedHashMap本質上也是一個HashMap,在HashMap的基礎上新增所有entry構成雙向迴圈連結串列的功能。因此LinkedHashMap並沒有覆寫HashMap的put方法,只是覆寫了HashMap中put方法呼叫的addEntry方法。
先來回顧下HashMap的put方法夠幹了什麼
public V put(K key, V value) {
if (key == null)
return putForNullKey(value);
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;
if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
V oldValue = e.value;
e.value = value;
e.recordAccess(this);
return oldValue;
}
}
modCount++;
addEntry(hash, key, value, i);
return null;
}
HashMap的put方法主要功能是計算對應key的桶的位置,遍歷桶中連結串列,找到對應key的entry修改原資料。遍歷連結串列結束沒有找到對應key的entry則呼叫addEntry方法將新新增的鍵值對entry新增到桶中entry連結串列的頭部。
而對於LinkedHashMap中的entry節點來說,要維持兩個連結串列,一個是桶中的next指標域連結的hask衝突的key構成的entry單連結串列,第二個就是維護所有entry構成的雙向迴圈連結串列
LinkedHashMap的addEntry方法如下所示
void addEntry(int hash, K key, V value, int bucketIndex) {
createEntry(hash, key, value, bucketIndex);
// Remove eldest entry if instructed, else grow capacity if appropriate
Entry<K,V> eldest = header.after;
if (removeEldestEntry(eldest)) {
removeEntryForKey(eldest.key);
} else {
if (size >= threshold)
resize(2 * table.length);
}
}
先呼叫createEntry把新節點連結到兩條連結串列上,然後判斷是否要刪除最近最久未訪問的節點(也就是雙向迴圈連結串列的第一個節點),要刪除的話就不需要檢查是否需要擴容了,都則要檢查是否需要擴容。
void createEntry(int hash, K key, V value, int bucketIndex) {
HashMap.Entry<K,V> old = table[bucketIndex];
Entry<K,V> e = new Entry<K,V>(hash, key, value, old);
table[bucketIndex] = e;
e.addBefore(header);
size++;
}
可見新節點被兩條連結串列都連結上了
第一條是單項非迴圈連結串列,桶中hash值衝突得key構成的entry連結串列
HashMap.Entry<K,V> old = table[bucketIndex];
Entry<K,V> e = new Entry<K,V>(hash, key, value, old);
table[bucketIndex] = e;
第二條是所有entry節點構成的雙向迴圈連結串列
e.addBefore(header);
containsValue方法直接在雙向迴圈連結串列中查詢值
public boolean containsValue(Object value) {
// Overridden to take advantage of faster iterator
if (value==null) {
for (Entry e = header.after; e != header; e = e.after)
if (e.value==null)
return true;
} else {
for (Entry e = header.after; e != header; e = e.after)
if (value.equals(e.value))
return true;
}
return false;
}
根據傳入引數value是否為null,對雙向迴圈連結串列進行遍歷查詢
相關文章
- JDK原始碼分析(四)——LinkedHashMapJDK原始碼HashMap
- JDK1.8 原始碼分析(九)--LinkedHashMapJDK原始碼HashMap
- LinkedHashMap 原始碼解讀(JDK 1.8)HashMap原始碼JDK
- 死磕 java集合之LinkedHashMap原始碼分析JavaHashMap原始碼
- jdk原始碼分析之TreeMapJDK原始碼
- 原始碼|jdk原始碼之HashMap分析(一)原始碼JDKHashMap
- 原始碼|jdk原始碼之HashMap分析(二)原始碼JDKHashMap
- java基礎:LinkedHashMap — 原始碼分析JavaHashMap原始碼
- 死磕 jdk原始碼之HashMap原始碼分析JDK原始碼HashMap
- JDK1.8原始碼分析之HashMapJDK原始碼HashMap
- 【JDK】JDK原始碼分析-ReentrantLockJDK原始碼ReentrantLock
- 原始碼|jdk原始碼之棧、佇列及ArrayDeque分析原始碼JDK佇列
- 【JDK原始碼分析系列】ArrayBlockingQueue原始碼分析JDK原始碼BloC
- 【JDK】JDK原始碼分析-AbstractQueuedSynchronizer(3)JDK原始碼
- 【JDK】JDK原始碼分析-AbstractQueuedSynchronizer(2)JDK原始碼
- 【JDK】JDK原始碼分析-AbstractQueuedSynchronizer(1)JDK原始碼
- JDK原始碼分析-TreeSetJDK原始碼
- Java——LinkedHashMap原始碼解析JavaHashMap原始碼
- LinkedHashMap原始碼解讀HashMap原始碼
- 搞懂 Java LinkedHashMap 原始碼JavaHashMap原始碼
- JDK中的BitMap實現之BitSet原始碼分析JDK原始碼
- JDK 1.6 HashMap 原始碼分析JDKHashMap原始碼
- JDK1.8原始碼分析03之idea搭建原始碼閱讀環境JDK原始碼Idea
- ConcurrentHashMap原始碼分析-JDK18HashMap原始碼JDK
- LinkedList原始碼分析(jdk1.8)原始碼JDK
- JDK1.8 hashMap原始碼分析JDKHashMap原始碼
- ArrayList原始碼分析(JDK1.8)原始碼JDK
- 原始碼分析–ArrayList(JDK1.8)原始碼JDK
- 原始碼分析–HashSet(JDK1.8)原始碼JDK
- HashMap原始碼分析 JDK1.8HashMap原始碼JDK
- ArrayList原始碼分析 jdk1.8原始碼JDK
- JDK 原始碼分析(1) Object類JDK原始碼Object
- HashMap原始碼分析(JDK8)HashMap原始碼JDK
- 原始碼分析系列1:HashMap原始碼分析(基於JDK1.8)原始碼HashMapJDK
- 併發程式設計之 ConcurrentHashMap(JDK 1.8) putVal 原始碼分析程式設計HashMapJDK原始碼
- JDK原始碼解析系列之objectJDK原始碼Object
- JDK1.8 原始碼分析(十) -- TreeMapJDK原始碼
- Android 原始碼分析之 AsyncTask 原始碼分析Android原始碼
- Guava 原始碼分析之 EventBus 原始碼分析Guava原始碼