本文代碼來自JDK8
性質
- LinkedHashMap 繼承於 HashMap, 具備 HashMap 的一切性質;
- LinkedHashMap 會按先後插入順序對元素排序遍歷;
- LinkedHashMap 會額外使用雙向鏈表結構來表示插入的元素.
變量
-
transient LinkedHashMap.Entry head
表示雙向鏈表的頭部 -
transient LinkedHashMap.Entry tail
表示雙向鏈表的尾部 -
final boolean accessOrder
true: 表示把最後訪問的節點放到雙向鏈表的最後一位, 訪問的方式有替換舊節點和讀取節點
put
LinkedHashMap 的 put 方法也是使用 HashMap 的方法, 不同在於重寫了 newNode(), afterNodeAccess 和 afterNodeInsertion 這幾個方法, 這幾個方法的調用可以看 HashMap-詳解四, 下面具體講講如何重寫這幾個方法.
newNode
/**
* 根據 key-value 創建雙向鏈表節點
* e 表示下一個節點, 不過這裏是空值, 不用理會
*/
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;
}
/**
* 繼承 HashMap 的靜態內部類 Node
*/
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) {
LinkedHashMap.Entry<K,V> last = tail;
tail = p;
// 如果這是空鏈表, 新節點就是頭結點
if (last == null)
head = p;
else {
p.before = last;
last.after = p;
}
}
afterNodeAccess
/**
* 1. 使用 get 方法會訪問到節點, 從而觸發調用這個方法
* 2. 使用 put 方法插入節點, 如果 key 存在, 也算要訪問節點, 從而觸發該方法
* 3. 只有 accessOrder 是 true 纔會調用該方法
* 4. 這個方法會把訪問到的最後節點重新插入到雙向鏈表結尾
*/
void afterNodeAccess(Node<K,V> e) { // move node to last
// 用 last 表示插入 e 前的尾節點
// 插入 e 後 e 是尾節點, 所以也是表示 e 的前一個節點
LinkedHashMap.Entry<K,V> last;
if (accessOrder && (last = tail) != e) {
// p: 當前節點
// b: 前一個節點
// a: 後一個節點
// 結構爲: b <=> p <=> a
LinkedHashMap.Entry<K,V> p =
(LinkedHashMap.Entry<K,V>)e, b = p.before, a = p.after;
// 結構變成: b <=> p <- a
p.after = null;
// 如果當前節點 p 本身是頭節點, 那麼頭結點要改成 a
if (b == null)
head = a;
// 如果 p 不是頭尾節點, 把前後節點連接, 變成: b -> a
else
b.after = a;
// a 非空, 和 b 連接, 變成: b <- a
if (a != null)
a.before = b;
// 如果 a 爲空, 說明 p 是尾節點, b 就是它的前一個節點, 符合 last 的定義
else
last = b;
// 如果這是空鏈表, p 改成頭結點
if (last == null)
head = p;
// 否則把 p 插入到鏈表尾部
else {
p.before = last;
last.after = p;
}
tail = p;
++modCount;
}
}
afterNodeInsertion
/**
* 插入新節點纔會觸發該方法
* 根據 HashMap 的 putVal 方法, evict 一直是 true
* removeEldestEntry 方法表示移除規則, 在 LinkedHashMap 裏一直返回 false
* 所以在 LinkedHashMap 裏這個方法相當於什麼都不做
*/
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);
}
}
LinkedHashMap 的遍歷方式和 HashMap 的一樣, 都是通過 entrySet 方法返回 Set 實例, 然後通過 iterator 方法返回迭代器進行遍歷.
entrySet
/**
* 返回 LinkedEntrySet 實例, 這是非靜態內部類
*/
public Set<Map.Entry<K,V>> entrySet() {
Set<Map.Entry<K,V>> es;
return (es = entrySet) == null ? (entrySet = new LinkedEntrySet()) : es;
}
/**
* 和 HashMap 的 EntrySet 類一樣繼承 AbstractSet
* iterator 方法返回 LinkedEntryIterator 實例
*/
final class LinkedEntrySet extends AbstractSet<Map.Entry<K,V>> {
...
public final Iterator<Map.Entry<K,V>> iterator() {
return new LinkedEntryIterator();
}
...
}
next 和 hasNext
/**
* next 方法實際是調用父類 nextNode 方法返回節點
*/
final class LinkedEntryIterator extends LinkedHashIterator
implements Iterator<Map.Entry<K,V>> {
public final Map.Entry<K,V> next() { return nextNode(); }
}
abstract class LinkedHashIterator {
LinkedHashMap.Entry<K,V> next;
LinkedHashMap.Entry<K,V> current;
int expectedModCount;
/**
* 構造函數, 從雙向鏈表頭節點開始遍歷
*/
LinkedHashIterator() {
next = head;
expectedModCount = modCount;
current = null;
}
public final boolean hasNext() {
return next != null;
}
/**
* 遍歷比較簡單, 直接讀取下一個節點就行
*/
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 = e.after;
return e;
}
...
}