與HashMap的區別:內部維持了一個雙向鏈表,可以保持順序
LinkedHashMap繼承自HashMap,很多操作都是跟HashMap一樣,對於一樣的地方就不再介紹,詳細可參考HashMap - - JDK1.8 源碼分析 ,下面主要介紹一下不同的地方。
數據結構
首先列出幾個需要知曉的實例變量含義:
final boolean accessOrder;//該變量表示是否需要按照讀取順序排序;true爲是,false爲否,當爲false時按照插入順序排序;此處的讀取包括更新(因爲更新也意味着一次讀取)和讀取
transient LinkedHashMap.Entry<K,V> head;//表示該實例的頭部節點
transient LinkedHashMap.Entry<K,V> tail;//表示該實例的尾部節點
開始源碼分析
一、構造函數
public LinkedHashMap(int initialCapacity, float loadFactor) {
super(initialCapacity, loadFactor);//直接調用父類構造方法
accessOrder = false;//默認爲false
}
public LinkedHashMap(int initialCapacity) {
super(initialCapacity);//直接調用父類構造方法
accessOrder = false;//默認爲false
}
public LinkedHashMap() {
super();//直接調用父類構造方法
accessOrder = false;//默認爲false
}
public LinkedHashMap(Map<? extends K, ? extends V> m) {
super();//直接調用父類構造方法
accessOrder = false;//默認爲false
putMapEntries(m, false);//調用父類的存儲元素方法
}
public LinkedHashMap(int initialCapacity,
float loadFactor,
boolean accessOrder) {
super(initialCapacity, loadFactor);//直接調用父類構造方法
this.accessOrder = accessOrder;//允許用戶自定義
}
對於調用父類的方法,此篇不再贅述,可看HashMap源碼講解那篇。
二、存儲元素-put方法
LinkedHashMap的put方法直接調用的父類HashMap的put方法,不過重寫了調用過程中的幾個方法,下面重點分析下這幾個方法。
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
Node<K,V>[] tab; Node<K,V> p; int n, i;
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);//被linkedhashmap重寫
else {
Node<K,V> e; K k;
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p;
else if (p instanceof TreeNode)
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
else {
for (int binCount = 0; ; ++binCount) {
if ((e = p.next) == null) {
p.next = newNode(hash, key, value, null);
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
treeifyBin(tab, hash);
break;
}
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)
e.value = value;
afterNodeAccess(e);//被linkedhashmap重寫
return oldValue;
}
}
++modCount;
if (++size > threshold)
resize();
afterNodeInsertion(evict);//被linkedhashmap重寫
return null;
}
1. newNode方法
Node<K,V> newNode(int hash, K key, V value, Node<K,V> e) {
LinkedHashMap.Entry<K,V> p =
new LinkedHashMap.Entry<K,V>(hash, key, value, e);//初始化實例
linkNodeLast(p);//設置前後節點
return p;
}
//linkedhashmap節點類
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 void linkNodeLast(LinkedHashMap.Entry<K,V> p) {
//將之前的尾結點賦值給last
LinkedHashMap.Entry<K,V> last = tail;
//將p設置爲該實例的尾節點變量
tail = p;
//如果之前尾節點爲空,說明當前這個map是空的,把該節點設置爲頭節點
if (last == null)
head = p;
else {//否則 把當前節點的前一節點設置爲之前的尾節點,把之前的尾節點的下一節點設置爲當前節點
p.before = last;
last.after = p;
}
}
2.afterNodeAccess方法
//該方法是爲了當accessOrder爲true時,每次訪問時更新被訪問節點的位置,使其到鏈表尾端
void afterNodeAccess(Node<K,V> e) { // move node to last
LinkedHashMap.Entry<K,V> last;
//下面代碼不再逐行分析,總之就是如果當前節點不是末尾,則把它原來的前一節點鏈接到它原來的後一節點,它鏈接到尾部
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;
}
}
上面的代碼可用如下圖所示(從圖中可以看到,結點3鏈接到了雙向鏈表原尾結點後面,變成了新的尾節點):
3. afterNodeInsertion方法
//該方法實現的是,如果evict爲ture,並且removeEldestEntry返回也爲true,則移除第一個節點,一般用於實現LRU(最近最少訪問)策略的緩存
//通過跟蹤代碼可知removeEldestEntry目前只會返回false,所以再不重寫removeEldestEntry的方法時,該方法沒有意義
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);
}
}
//目前直接返回的false
protected boolean removeEldestEntry(Map.Entry<K,V> eldest) {
return false;
}
三、remove方法和hashmap一致
四、get方法
linkedHashMap中重寫了get方法,但其中的getNod方法使用的還是hashMap中的getNode方法
public V get(Object key) {
Node<K,V> e;
if ((e = getNode(hash(key), key)) == null)//還用了hashmap的getNode方法
return null;
if (accessOrder)//增加了如果accessOrder爲true的情況,會將被訪問對象更新到鏈的尾端
afterNodeAccess(e);
return e.value;
}
附:LinkedHashMap實現LRU的例子可參考https://www.jianshu.com/p/816ea80a8cbc