java常用類---LinkedHashMap源碼分析及用法

LinkedHashMap本身繼承了HashMap,除了HashMap的特點,有兩個特性:

  • 按照 插入順序進行訪問
  • 實現了訪問最少最先刪除

一、按照 插入順序進行訪問

與HashMap相比,新增瞭如下屬性:

// 鏈表頭
transient LinkedHashMap.Entry<K,V> head;
// 鏈表尾
transient LinkedHashMap.Entry<K,V> tail;
// 繼承 Node,爲數組的每個元素增加了 before 和 after 屬性
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;

從新增的屬性可以查看,LinkedHashMap的數據結構就像是把LinkedList的元素換成了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);
        }
    }

按照順序新增

LinkedHashMap初始化時,默認accessOrder爲false,即按照插入順序進行訪問,插入訪問使用的就是HashMap的put方法,不過對put中調用的newNode/newTreeNode進行了重寫,保證新增元素追加到鏈表的尾部,從而保證了按照順序新增;

// 新增節點,並追加到鏈表的尾部
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;
    // last 爲空,說明鏈表爲空,首尾節點相等
    if (last == null)
        head = p;
    // 鏈表有數據,直接建立新增節點和上個尾節點之間的前後關係即可
    else {
        p.before = last;
        last.after = p;
    }
}

按照順序訪問

LinkedHashMap僅僅提供了單向訪問,即按照插入的順序從頭到尾進行訪問,一般情況下,都是通過迭代器進行訪問,默認從頭結點開始訪問,依次訪問當前節點的after節點即可;

Map對key,value,entity節點都有對應的迭代方法,以迭代entity爲例,可以使用LinkedHashMap.entrySet().iterator()這種寫法返回LinkedHashIterator

// 初始化時,默認從頭節點開始訪問
LinkedHashIterator() {
    // 頭節點作爲第一個訪問的節點
    next = head;
    expectedModCount = modCount;
    current = 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; // 通過鏈表的 after 結構,找到下一個迭代的節點
    return e;
}

二、訪問最少刪除策略

這種策略也叫作LRU,即Least Recent Used最近最少使用策略,即經常訪問的元素會被追加到隊尾,從而不經常訪問的就位於隊頭,然後根據刪除策略,譬如map元素大於多少時,把頭節點刪除,demo如下:

public void testAccessOrder() {
  // 新建 LinkedHashMap
  LinkedHashMap<Integer, Integer> map = new LinkedHashMap<Integer, Integer>(4,0.75f,true) {
    {
      put(10, 10);
      put(9, 9);
      put(20, 20);
      put(1, 1);
    }
    @Override
    // 覆寫了刪除策略的方法,我們設定當節點個數大於 3 時,就開始刪除頭節點
    protected boolean removeEldestEntry(Map.Entry<Integer, Integer> eldest) {
      return size() > 3;
    }
  };
  log.info("初始化:{}",JSON.toJSONString(map));
  Assert.assertNotNull(map.get(9));
  log.info("map.get(9):{}",JSON.toJSONString(map));
  Assert.assertNotNull(map.get(20));
  log.info("map.get(20):{}",JSON.toJSONString(map));
}

輸出結果如下:

初始化:{9:9,20:20,1:1}
map.get(9):{20:20,1:1,9:9}
map.get(20):{1:1,9:9,20:20}

看明白了嗎?我簡單解釋一下,初始化的時候,四個元素,但由於重寫了刪除策略,即元素個數大於3的時候,就要刪除頭結點元素,從而(10,10)被刪除,當我們調用get(9)時,元素9移動到隊尾,當我們調用get(20)時,元素20移動到隊尾,從而體現了經常使用的元素被移動到隊尾的特點;

現在來看下源代碼,看看get的時候節點或者元素如何被移動到隊尾

public V get(Object key) {
    Node<K,V> e;
    // 調用 HashMap  get 方法
    if ((e = getNode(hash(key), key)) == null)
        return null;
    // 如果設置了 LRU 策略
    if (accessOrder)
    // 這個方法把當前 key 移動到隊尾
        afterNodeAccess(e);
    return e.value;
}
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;
        }
    }

LinkedHashMap本身是是沒有put方法的,調用的是HashMap的put方法,但是實現了put方法中afterNodeInsertion的方法:

// 刪除很少被訪問的元素,被 HashMap 的 put 方法所調用
void afterNodeInsertion(boolean evict) { 
    // 得到元素頭節點
    LinkedHashMap.Entry<K,V> first;
    // removeEldestEntry 來控制刪除策略,如果隊列不爲空,並且刪除策略允許刪除的情況下,刪除頭節點
    if (evict && (first = head) != null && removeEldestEntry(first)) {
        K key = first.key;
        // removeNode 刪除頭節點
        removeNode(hash(key), key, null, false, true);
    }
}

 

發佈了95 篇原創文章 · 獲贊 15 · 訪問量 5259
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章