重識java-LinkedHashMap
使用場景
如果需要使用的Map中的key無序,選擇HashMap
;如果要求key有序,則選擇TreeMap
。
但是選擇TreeMap就會有性能問題,因爲TreeMap的get操作的時間複雜度是O(log(n))
的,相比於HashMap的O(1)
還是差不少的,LinkedHashMap
的出現就是爲了平衡這些因素,使得
能夠以O(1)時間複雜度增加查找元素,又能夠保證key的有序性
此外,LinkedHashMap提供了兩種key的順序:
- 訪問順序(access order)。非常實用,可以使用這種順序實現
LRU(Least Recently Used)
緩存 - 插入順序(insertion orde)。同一key的多次插入,並不會影響其順序
源代碼解讀
類聲明
public class LinkedHashMap<K,V>
extends HashMap<K,V>
implements Map<K,V>
繼承自HashMap,此處實現Map接口,用來表明該類是Map系的類。
功能和特點
LinkedHashMap
中採用的這種環型雙向鏈表.- key有序,並且get時間複雜度爲O(1)。
- 有兩種記錄順序的方式,一種是訪問順序,一種是key插入的順序。
常量
final boolean accessOrder;//true:key的順序爲訪問順序;false:key的順序爲插入式順序。默認爲插入順序
transient LinkedHashMap.Entry<K,V> head;//雙向鏈表頭結點
transient LinkedHashMap.Entry<K,V> tail;//雙向鏈表尾結點
構造函數
public LinkedHashMap(int initialCapacity, float loadFactor) {
super(initialCapacity, loadFactor);
//默認爲false,也就是插入順序
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;
}
節點數據結構
主要基於HashMap的節點數據結構實現。
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);
}
}
雙向鏈表實現的LinkedHshMap,所以每個節點須在HashMap的基礎上添加指向前繼節點與後繼節點指針:before,after。
private void linkNodeLast(LinkedHashMap.Entry<K,V> p) {
LinkedHashMap.Entry<K,V> last = tail;
tail = p;
if (last == null)
head = p;
else {
p.before = last;
last.after = p;
}
}
在最後添加一個節點,需要取LinkedHashMap的末尾節點,
- 雙向鏈表爲空(末爲節點爲空),這新添加的既是頭節點,也是尾節點;
- 如果不爲空,p的前繼指向原最後一個節點,最後一個節點的後繼指向p。
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;
}
刪除一個節點時,需要把
- 前繼節點的後繼指針 指向 要刪除節點的後繼節點
- 後繼節點的前繼指針 指向 要刪除節點的前繼節點
核心方法
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;
}
移除節點後調用的,就是將節點從雙向鏈表中刪除
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);
}
}
如果用戶定義了removeEldestEntry
的規則,那麼便可以執行相應的移除操作。
afterNodeAccess
void afterNodeAccess(Node<K,V> e) { // move node to last
LinkedHashMap.Entry<K,V> last;
// 如果定義了accessOrder,那麼就保證最近訪問節點放到最後
if (accessOrder && (last = tail) != e) {
LinkedHashMap.Entry<K,V> p =
(LinkedHashMap.Entry<K,V>)e, b = p.before, a = p.after;
p.after = null;
//e的前驅還有節點,e不是頭結點,否則e是頭結點。
if (b == null)
head = a;//把頭結點後移一個
else
b.after = a;//把中間節點移除
//e是不是tail節點,是tail節點,則使last爲e的前驅
if (a != null)
a.before = b;
else
last = b;
//鏈表中只有節點e
if (last == null)
head = p;
else {//將p移到最後
p.before = last;
last.after = p;
}
tail = p;
++modCount;
}
}
進行put之後,對節點的訪問了,那麼這個時候就會更新鏈表,把最近訪問的放到最後,保證鏈表的key按照訪問有序。
put
put
函數在LinkedHashMap
中未重新實現,只是實現了afterNodeAccess
和afterNodeInsertion
兩個回調函數。
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
函數重新實現並加入了afterNodeAccess
來保證訪問順序
總結
- 怎樣保證插入順序?
使用前驅和後繼指針,使得原來的HashMap有序,在
LinkedHashMap
中覆蓋HashMap
中newNode
方法,使得每次put數據時,新建的節點都是LinkedHashMap.Entry<K,v>
類型的,比普通的HsahMap.Entry
多一個前驅結點和一個後繼節點,使用前驅和後繼保證插入有序。 - 怎麼樣保證訪問順序?
覆蓋父類
HashMap
的afterNodeAccess
方法,使得每次訪問後,都改變鏈表順序。使得原鏈表按訪問排序。將最新一次訪問的節點放到鏈表的最後。
Thanks for reading! want more