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);
    }
}

 

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