Java集合之LinkedHashMap和TreeMap源碼解析

目錄

1.TreeMap

1.1TreeMap整體架構

1.2新增節點的源碼解析

2.LinkedHashMap

2.1整體結構

2.1按照插入順序訪問

2.2訪問最少刪除原則(LRU)


1.TreeMap


1.1TreeMap整體架構

  • 底層數據結構使用了紅黑樹來進行實現,是一個有序且基本平衡的二叉排序樹,可以維護節點的大小
//比較器,如果外部有傳進來 Comparator 比較器,首先用外部的
//如果外部比較器爲空,則使用 key 自己實現的 Comparable#compareTo 方法
private final Comparator<? super K> comparator;

//紅黑樹的根節點
private transient Entry<K,V> root;

//紅黑樹的已有元素大小
private transient int size = 0;

//樹結構變化的版本號,用於迭代過程中的快速失敗場景
private transient int modCount = 0;

//紅黑樹的節點
static final class Entry<K,V> node ; // implements Map.Entry<K,V> {}

1.2新增節點的源碼解析

  • 初識判斷根節點是否爲空
Entry<K,V> t = root;
//紅黑樹根節點爲空,直接新建
if (t == null) {
    // compare 方法限制了 key 不能爲 null
    compare(key, key); // type (and possibly null) check
    // 成爲根節點
    root = new Entry<>(key, value, null);
    size = 1;
    modCount++;
    return null;
}
  • 使用比較器或者自身實現的Compareable接口來進行判斷與當前節點的大小關係
  • 如果值相等直接進行覆蓋val,最後會達到葉子節點停止迭代
Comparator<? super K> cpr = comparator;
if (cpr != null) {
    //自旋找到 key 應該新增的位置,就是應該掛載那個節點的頭上
    do {
        //一次循環結束時,parent 就是上次比過的對象
        parent = t;
        // 通過 compare 來比較 key 的大小
        cmp = cpr.compare(key, t.key);
        //key 小於 t,把 t 左邊的值賦予 t,因爲紅黑樹左邊的值比較小,循環再比
        if (cmp < 0)
            t = t.left;
        //key 大於 t,把 t 右邊的值賦予 t,因爲紅黑樹右邊的值比較大,循環再比
        else if (cmp > 0)
            t = t.right;
        //如果相等的話,直接覆蓋原值
        else
            return t.setValue(value);
        // t 爲空,說明已經到葉子節點了
    } while (t != null);
}
  • 進行節點的指針更新和着色處理
  • 如果新增後破壞了平衡會進行着色旋轉來達到平衡
  • 最後,進行版本號和長度的更新
//設置新增節點
Entry<K,V> e = new Entry<>(key, value, parent);
//cmp 代表最後一次對比的大小,小於 0 ,代表 e 在上一節點的左邊
if (cmp < 0)
    parent.left = e;
//cmp 代表最後一次對比的大小,大於 0 ,代表 e 在上一節點的右邊,相等的情況第二步已經處理了。
else
    parent.right = e;
//判斷是否平衡
fixAfterInsertion(e);
//版本號和長度進行更新
size++;
modCount++;
return null;

2.LinkedHashMap


2.1整體結構

  • 繼承了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;

2.1按照插入順序訪問

ps:元素順序只按照插入順序來排列,accessOrder值爲false,且默認爲false

  • 每次新增節點加到鏈表尾部即可(需要注意,判斷鏈表爲空)
// 新增節點,並追加到鏈表的尾部
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;
    }
}

先新創建一個節點,在更新尾節點,

如果尾節點本身就爲空,就更新頭節點,

如果不爲空,需要舊的尾節點的after指針指向新的尾節點

  • 每次只需要訪問after指針即可
// 初始化時,默認從頭節點開始訪問
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();
    //指代hasNext方法
    if (e == null)
        throw new NoSuchElementException();
    //更新當前節點指針和下一個節點指針
    current = e;
    next = e.after; // 通過鏈表的 after 結構,找到下一個迭代的節點
    return e;
}

先初始化迭代器,設置默認的期望版本號,更新next節點爲頭節點,當前節點爲null

nextNode方法:先檢驗版本號,在檢驗是否可以繼續迭代hasNext方法,更新當前節點指針和下一個節點指針,返回當前節點。

2.2訪問最少刪除原則(LRU)

ps:每次新插入和新訪問一個元素都會加到鏈表尾部,最不長訪問的就會靠近在頭部

//測試demo:
LinkedHashMap<String, String> aaaaa = new LinkedHashMap<>(3, 0.75f, true);
aaaaa.put("1", "111");
aaaaa.put("2", "111");
aaaaa.put("3", "111");
aaaaa.get("1");
aaaaa.put("4", "111");
aaaaa.get("3");
Set< String> abc = aaaaa.keySet();
System.out.println(abc);
  • 元素被轉移到隊尾
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;
}
  • 刪除很少被訪問的節點
// 刪除很少被訪問的元素,被 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);
    }
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章