原 薦 聊聊LinkedHashMap
LinkedHashMap簡介
LinkedHashMap是一個根據某種規則有序的hashmap。根據名字,我們也可以看出這個集合是有hash雜湊的功能的同時也有順序。hashmap是無法根據某種順序來訪問資料的,例如放入集合的元素先後的順序。list都有這個功能,可以根據放入集合的先後來訪問具體的資料。這裡大家也肯定是有疑問的,例如都已經使用了hash了,為什麼還要去保證順序訪問。這個在後面的場景中解釋。
LinkedHashMap的實現
當剛遇到這個集合的時候,我也疑惑,能同時滿足條件的資料結構究竟是怎麼樣的。如果沒有思考這個問題,還請看到這裡好好想想。 ---------滑稽分割-------- 答案確實是沒有這樣的資料結構。他是兩種結構的組合。一種是我們熟悉的hashmap。另外一種就是連結串列。資料存入集合的時候,先根據hashmap的流程存放入陣列中。然後再根據連結串列的原則,進行連結。 如果看原始碼,也會發現其實沒有多少方法。基本都是繼承自hashmap的。
public class LinkedHashMap<K,V> extends HashMap<K,V> implements Map<K,V>
我們來看看串聯邏輯的幾個操作
節點組成
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); } }
可以看出。相比hashmap的節點。linkedhashmap主要增加before, after。可以組成一個雙向連結串列。
節點加入
private void linkNodeLast(LinkedHashMap.Entry<K,V> p) { LinkedHashMap.Entry<K,V> last = tail;//獲取最後一個節點 tail = p;//tail指向新加入的節點 if (last == null)//連結串列為空 head = p; else { p.before = last; last.after = p; } } //新建節點的時候把節點加入了雙向連結串列 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; }
刪除情況也是類似。
節點訪問
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; }
afterNodeAccess中主要是把訪問的節點從原來的位置摘除,加入到尾節點,成為連結串列的最後一個元素。
使用場景
順序遍歷和快速定位
LinkedHashMap適合有加入順序和快速定位的場景。我自己開發中遇到過一個場景,就是把配置順序讀取,需要按照讀取的順序訪問,而且還需要根據值key直接獲取值。這個場景就需要使用LinkedHashMap。
快取
LinkedHashMap另外一個強大的功能就是做快取。不過我們要繼承一下。去重寫一個方法。
protected boolean removeEldestEntry(Map.Entry<K,V> eldest) {//預設是沒有操作 return false; }
這個方法在插入之後會被呼叫到。
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支援兩種快取策略。FIFO和LRU。大家應該也猜到控制策略的地方就是accessOrder。預設為false。就是FIFO。設定為true時就是LRU。因為在訪問的時候會調整連結串列結構,呼叫afterNodeAccess會把訪問的節點放入佇列最後。所以每次刪除first就可以達到效果。我一般會選擇繼承LinkedHashMap。然後重寫removeEldestEntry,例如可以元素個數達到200範圍true。這裡需要根據具體場景來編寫。
