類介紹(註釋)
- add、contains、remove 方法,時間複雜度是O(1)。
- LinkedHashMap的遍歷耗時,與_capacity無關,與map的size(元素多少)呈線性。_
- HashMap的遍歷,可能比_LinkedHashMap更耗時,其和_capacity呈線性關係。
- LinkedHashMap是非線程安全的,併發出錯時,會快速失敗,拋出
ConcurrentModificationException
。可以使用_Collections.synchronizedMap(new LinkedHashMap(…));_ - LinkedHashMap非常適合於構建
LRU緩存
(least-recently Used)。 - 並不是所有的adds、delete操作都會造成LinkedHashMap結構的變更。
_insertion-ordered 模式下,_修改一個已經存在的key,對應的值,並不會造成結構的變更
access-ordered 模式下,get將造成結構的變更
LinkedHashMap的一些概念
```java // 頭結點,同時也是最早插入的節點。 transient LinkedHashMap.Entry
// 尾結點,同時也是最後插入的節點。
transient LinkedHashMap.Entry<K,V> tail;
// 繼承 Node,爲數組的每個元素增加了 before 和 after 屬性。
// LinkedHashMap 的數據結構很像是把 LinkedList 的每個元素換成了 HashMap 的 Node,像是兩者的結合體.
// 也正是因爲增加了這些結構,從而能把 Map 的元素都串聯起來,形成一個鏈表,而鏈表就可以保證順序了,就可以維護元素插入進來的順序。
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);
}
}
// 控制兩種訪問模式的字段,默認 false
// true 按照訪問順序,會把經常訪問的 key 放到隊尾
// false 按照插入順序提供訪問
final boolean accessOrder;
<a name="rquVh"></a>
# 常用方法分析
<a name="yKIWn"></a>
## LinkedHashMap() (無參構造方法)
LinkedHashMap 在無參構造方法中,將accessOrder設置爲false,即按照插入順序訪問。它的_capacity、與HashMap的默認的一致,爲16、0.75。
```java
public LinkedHashMap() {
super();
accessOrder = false;
}
LinkedHashMap按順序插入節點(put)
首先,put是直接調用HashMap#put
方法。從LinkedHashMap 的重要概念中,可以看到,存在一個繼承了HashMap.Node的Entry。
當前put時,即會創建LinkedHashMap#Entry對象。並且,LinkedHashMap中重寫了HashMap#put中調用的newNode
、afterNodeInsertion
、afterNodeAccess
方法。
put的具體流程,可以參照HashMap源碼。這裏來看LinkedHashMap重寫的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;
}
// link at the end of list
private void linkNodeLast(LinkedHashMap.Entry<K,V> p) {
// 備份尾節點
LinkedHashMap.Entry<K,V> last = tail;
// 將新插入的節點,作爲尾節點
tail = p;
// 如果原先尾節點爲null(原先鏈表爲空,現在有一個元素)
if (last == null)
// 將p也作爲頭結點(只有一個元素的鏈表,head=tail=唯一的元素)
head = p;
else {
// 鏈表有數據,則建立 前後 指向
p.before = last;
last.after = p;
}
}
LinkedHashMap 通過新尾節點,給每個節點增加 before、after 屬性,每次新增時,都把節點追加到尾節點等手段,在新增的時候,就已經維護了按照插入順序的鏈表結構了。
遍歷
LinkedHashMap可以通過以下,實現的Map接口中的方法,進行遍歷。
當然,也可以通過迭代器來進行遍歷。如map.entrySet().iterator();
得到迭代器,再通過hasNext方法判斷是否有下一個節點後,最後通過next來訪問下一個節點。
其中的實現都依賴LinkedHashIterator。
final class LinkedEntryIterator extends LinkedHashIterator implements Iterator<Map.Entry<K,V>> {
public final Map.Entry<K,V> next() {
return nextNode();
}
}
// 下個節點
LinkedHashMap.Entry<K,V> next;
// 當前節點
LinkedHashMap.Entry<K,V> current;
// 期望的結構版本號
int expectedModCount;
// 構造方法
LinkedHashIterator() {
// 初始化時,頭結點即爲 next
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();
// 有下個節點,通過e.after,取得下個節點,並賦值給next
current = e;
next = e.after;
// 返回當前節點
return e;
}
LRU 最近最少使用
使用示例
這裏,是採用了LinkedHashMap的三個參數的構造方法,其源碼如下。其中最主要的,是指定了accessOrder
爲true
(默認爲false)。 true 按照訪問順序,會把經常訪問的 key 放到隊尾,get時會造成結構的變更; false 按照插入順序提供訪問。
public LinkedHashMap(int initialCapacity,
float loadFactor,
boolean accessOrder) {
super(initialCapacity, loadFactor);
this.accessOrder = accessOrder;
}
LRU大概的意思就是經常訪問的元素會被追加到隊尾,這樣不經常訪問的數據自然就靠近隊頭,然後我們可以通過設置刪除策略,比如當 Map 元素個數大於3時。
public static void main(String[] args) {
// 初始化時,主要指定了accessOrder爲true
LinkedHashMap<String, Object> map = new LinkedHashMap<String, Object>(16,0.75f,true) {
@Override
protected boolean removeEldestEntry(Map.Entry<String, Object> eldest) {
return this.size() > 3;
}
};
// 放入元素
map.put("1",1);
map.put("2",2);
map.put("3",3);
map.put("4",4);
// 輸出,並測試LRU策略
System.out.println(map);
map.get("2");
System.out.println(map);
map.get("3");
System.out.println(map);
map.get("4");
System.out.println(map);
}
// {2=2, 3=3, 4=4}
// {3=3, 4=4, 2=2}
// {4=4, 2=2, 3=3}
// {2=2, 3=3, 4=4}
可以看到:我們往map中放置四個元素,但輸出結果只有三個元素。1 不見了,這個是因爲在每次put時,會調用afterNodeInsertion
方法。該方法在允許刪除節點時,並在removeEldestEntry
方法返回true的情況下,(我們在用例中重寫了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);
}
}
當我們調用get方法後,發現輸出的元素順序發生改變。被訪問元素移動到鏈表尾部,這個體現了最經常被訪問的節點會被移動到鏈表尾部。在調用get後,頭節點即爲最早插入-最少被訪問的節點。
get方法源碼
public V get(Object key) {
Node<K,V> e;
if ((e = getNode(hash(key), key)) == null)
return null;
// accessOrder爲true時,纔會去將訪問的元素,放到鏈表尾部
if (accessOrder)
afterNodeAccess(e);
return e.value;
}