TreeMap源码浅析

1.源码简述

  • TreeMap的排序可以基于对象自身实现的Comparable接口,也可以基于传入的比较器,两者都没有,那么会报错(cannot be cast to java.lang.Comparable)。
  • modCount 是用于fail fast。主要用于检测并发修改异常问题
  • 基于自定义比较器比较,key是否为null,可以自己决定,但是如果使用的是对象实现的Comparable接口,那么key不能为null。
  • 删除分为三种情况,当被删除元素有两个子节点时,是通过后继节点进行删除的。(红黑树的红黑标记和旋转没有研究)

2.重要成员变量

// 自定义的排序比较器 创建TreeMap时传入
private final Comparator<? super K> comparator;

// 并发修改判断值
private transient int modCount = 0;

// 树形map的根节点
private transient Entry<K,V> root;

3. put(key,value) 新增元素

public V put(K key, V value) {
    Entry<K,V> t = root;
    if (t == null) {
				// 这段代码用于检测comparator是否存在,如果不存在那么对象需要实现Comparable接口
				// 否则没有比较的意义
        compare(key, key); // type (and possibly null) check
				// 根节点赋值
        root = new Entry<>(key, value, null);
        size = 1;
        modCount++;
        return null;
    }
    int cmp;
    Entry<K,V> parent;
    // split comparator and comparable paths
    Comparator<? super K> cpr = comparator;
    if (cpr != null) {
				// 先找是否存在,不存在找该元素要插入位置的父节点
				// 基于比较器比较
        do {
            parent = t;
            cmp = cpr.compare(key, t.key);
            if (cmp < 0)
								// 小的放左边
                t = t.left;
            else if (cmp > 0)
								// 大的放右边
                t = t.right;
            else
								// treeMap中存在时,则直接进行值覆盖
                return t.setValue(value);
        } while (t != null);
    }
    else {
				// 基于Comparable接口的对象 key 不能为null值
        if (key == null)
            throw new NullPointerException();
        @SuppressWarnings("unchecked")
            Comparable<? super K> k = (Comparable<? super K>) key;
				// 先找是否存在,不存在找该元素要插入位置的父节点
        do {
            parent = t;
            cmp = k.compareTo(t.key);
            if (cmp < 0)
								// 小的放左边
                t = t.left;
            else if (cmp > 0)
								// 大的放右边
                t = t.right;
            else
								// treeMap中存在时,则直接进行值覆盖
                return t.setValue(value);
        } while (t != null);
    }
    Entry<K,V> e = new Entry<>(key, value, parent);
		// 插入到父节点的左边或者右边
    if (cmp < 0)
        parent.left = e;
    else
        parent.right = e;
		// 红黑树规则调整
    fixAfterInsertion(e);
    size++;
    modCount++;
    return null;
}

流程:

  1. 针对空根节点进行操作
  2. TreeMap中找相同key,存在则覆盖并返回
  3. TreeMap中找要插入元素的父节点
  4. 在父节点中的左右子节点进行插入
  5. 红黑树规则平衡

3.get(Object key) 查找元素

public V get(Object key) {
    Entry<K,V> p = getEntry(key);
    return (p==null ? null : p.value);
}
final Entry<K,V> getEntry(Object key) {
    // Offload comparator-based version for sake of performance
    if (comparator != null)
				// 基于comparator比较器查找
        return getEntryUsingComparator(key);
    if (key == null)
        throw new NullPointerException();
    @SuppressWarnings("unchecked")
        Comparable<? super K> k = (Comparable<? super K>) key;
    Entry<K,V> p = root;
		// 基于Comparable接口的compareTo方法进行大小比较,查找元素
    while (p != null) {
        int cmp = k.compareTo(p.key);
        if (cmp < 0)
            p = p.left;
        else if (cmp > 0)
            p = p.right;
        else
            return p;
    }
    return null;
}

查找的过程比较简单,就是便利二叉树。是不过比较条件一个是传入的comparator比较器,一个是自身的Comparable接口实现。


4.remove(Object key) 删除接口

public V remove(Object key) {
		// 查找到对应元素
    Entry<K,V> p = getEntry(key);
    if (p == null)
        return null;

    V oldValue = p.value;
		// 删除树元素
    deleteEntry(p);
		// 返回删除元素
    return oldValue;
}

删除元素:

规则:

被删除元素P的左子节点都小于被删除节点

被删除元素P的左子节点都大于被删除节点的父节点

被删除元素的右子节点都大于被删除元素节点的父节点

被删除元素的右子节点都大于被删除元素节点的父节点

情景

1.当前节点没有子节点,直接删除

2.当前节点有一个子节点,直接删除当前节点,并将子节点顶上就行了

3.当前节点有两个节点:

找前继节点(小于被删除元素的最大值,也就是被删除元素的左子节点的最右节点),将该节点替换到被删除元素,然后原被删除节点的左子节点被附加到前继节点的最左子节点上。

或者找后继节点(大于被删除元素的最小值,也就是被删除元素的右子节点的最左节点),将该节点替换到被删除元素,然后原被删除节点的右子节点被附加到后继节点的最右子节点上

private void deleteEntry(Entry<K,V> p) {
    modCount++;
    size--;

    // If strictly internal, copy successor's element to p and then make p
    // point to successor.
    if (p.left != null && p.right != null) {
				// 找后继节点
        Entry<K,V> s = successor(p);
        p.key = s.key;
        p.value = s.value;
				// 后继节点赋值给被删除对象
        p = s;
    } // p has 2 children

    // 下面这块是进行元素删除的过程,总共有三种情景
		// 1. 当前元素无子节点
		// 2. 当前元素只有一个子节点
		// 3.当前元素有两个子节点,则需要通过使用后继节点的方式进行元素删除
		
		// 在下面5,6,7节中分开分析
}
//在删除节点中会调用这个方法,主要是用来找后继节点的
static <K,V> TreeMap.Entry<K,V> successor(Entry<K,V> t) {
  if (t == null)
      return null;
  else if (t.right != null) {
			// 右子节点的最左子节点
      Entry<K,V> p = t.right;
      while (p.left != null)
          p = p.left;
			//找到后继节点
      return p;
  } else {
      Entry<K,V> p = t.parent;
      Entry<K,V> ch = t;
      while (p != null && ch == p.right) {
          ch = p;
          p = p.parent;
      }
      return p;
  }
}

5.被删除节点为叶子节点时

Entry<K,V> replacement = (p.left != null ? p.left : p.right);
// 此时replacement为null
if (replacement != null) {
    // 这段不会走
} else if (p.parent == null) { // return if we are the only node.
    root = null; // 为根元素时,直接将根元素置为null
} else { //  No children. Use self as phantom replacement and unlink.
    if (p.color == BLACK)
				// 红黑树旋转与
        fixAfterDeletion(p);
		// 非根元素
    if (p.parent != null) {
        if (p == p.parent.left)
				// 被删除元素为父元素的左子节点时,父元素的左子节点进行删除
            p.parent.left = null;
        else if (p == p.parent.right)
				// 被删除元素为父元素的右子节点时,父元素的右子节点进行删除
            p.parent.right = null;
        p.parent = null; // 解除p与p.parent的连接
    }
}

6.被删除节点的子节点为单节点时

// 当前节点只有一个子节点时
// 只需要将当前节点的子节点与当前节点的父节点进行连接即可

// 获取子节点
Entry<K,V> replacement = (p.left != null ? p.left : p.right);

if (replacement != null) {
    // Link replacement to parent
		// 被删除节点的子节点的父节点指向被删除节点的父节点
    replacement.parent = p.parent;
    if (p.parent == null)
				// 如果被删除元素就是根节点,则replacement晋升为根节点
        root = replacement;
    else if (p == p.parent.left)
				// 如果被删除元素为其父节点的左子节点,则父节点的左子节点连接到被删除元素的子节点上
        p.parent.left  = replacement;
    else
				// 如果被删除元素为其父节点的左子节点,则父节点的左子节点连接到被删除元素的子节点上
        p.parent.right = replacement;

    // 解除被删除元素的连接关系
    p.left = p.right = p.parent = null;

    // Fix replacement
    if (p.color == BLACK)
				// 红黑树的红黑控制与左右旋转
        fixAfterDeletion(replacement);
} else if (p.parent == null) { // return if we are the only node.
		// 不会走到
} else { //  No children. Use self as phantom replacement and unlink.
    // 不会走到
}

7.被删除元素有两个子节点(情景1:后继节点没有右子节点)

后继节点是不可能有左子节点的

// 在successor方法中获取后继节点
// 将后继节点的键与值复制给被删除节点
// 将p指向后继节点(此时被删除的元素变为后继节点了,因为后继节点的值已经给)
// 这里的p已经不是被删除元素,而是后继节点

// 此时,后继节点没有子元素
Entry<K,V> replacement = null;

if (replacement != null) {
   //这里不会走到
} else if (p.parent == null) { // return if we are the only node.
    root = null; // 后继节点的父节点不可能为null ,所以这里走不到
} else { //  No children. Use self as phantom replacement and unlink.
    if (p.color == BLACK)
				// 红黑树旋转与红黑关系修改
        fixAfterDeletion(p);
		// 非根元素
    if (p.parent != null) {
				
        if (p == p.parent.left)
				// 后继节点为父元素的左子节点时,对父元素的左子节点进行删除
            p.parent.left = null;
        else if (p == p.parent.right)
				// 后继节点为父元素的右子节点时,对父元素的右子节点进行删除
            p.parent.right = null;
        p.parent = null; // 解除后继节点与父节点之间的连接
    }
}

8.被删除元素有两个子节点(情景2:后继节点有右子节点)

在后继节点有右子节点的情况下

// 后继节点有右子节点,所以这里replacement不为null
Entry<K,V> replacement =  p.right;

if (replacement != null) {
    // 将后继节点的子节点与后继节点的父节点关联
    replacement.parent = p.parent;
    if (p.parent == null)
				// 这个走不到
        root = replacement;
    else if (p == p.parent.left)
				// p一定是右子节点,走不到
        p.parent.left  = replacement;
    else
				// 后继节点的父节点的右子节点指向后继节点的子节点
        p.parent.right = replacement;

    // 解除后继节点与子节点和父节点之间的关联
    p.left = p.right = p.parent = null;

    // Fix replacement
    if (p.color == BLACK)
				// 红黑关系调整和左右旋转
        fixAfterDeletion(replacement);
} else if (p.parent == null) { // return if we are the only node.
   // 不走
} else { //  No children. Use self as phantom replacement and unlink.
    // 不走
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值