【JDK源码】TreeMap

    • 左旋
  • 右旋

  • get()

  • remove()

  • 总结TreeMap特性

TreeMap与HashMap【JDK源码】HashMap

TreeMap

继承体系

在这里插入图片描述

  • SortedMap 是一个扩展自 Map 的一个接口,对该接口的实现要保证所有的 Key 是完全有序的(也就是说SortedMap规定了元素可以按key的大小来遍历)。这个顺序一般是指 Key 的自然序(实现 Comparable 接口)或在创建 SortedMap 时指定一个比较器(Comparator)。当我们元素迭代时,就可以按序访问其中的元素。

  • NavigableMap 是 JDK 1.6 之后新增的接口,扩展了 SortedMap 接口,提供了一些导航方法。 descendingMap() 和 descendingKeySet() 则会获取和原来的顺序相反的集合,集合中的元素则是同样的引用,在该视图上的修改会影响到原始的数据。NavigableMap 可以按照 Key 的升序或降序进行访问和遍历。

基本属性

/**

  • 比较器,因为TreeMap是有序的,通过comparator接口我们可以对TreeMap的内部排序进行控制

*/

private final Comparator<? super K> comparator;

/**

*TreeMap红-黑节点,为TreeMap的内部类 root红黑树的根节点

*/

private transient Entry<K,V> root;

/**

  • TreeMap中存放的键值对的数量

*/

private transient int size = 0;

/**

  • 修改的次数

*/

private transient int modCount = 0;

(1)comparator

按key的大小排序有两种方式,一种是key实现Comparable接口,一种方式通过构造方法传入比较器

(2)root

根节点,TreeMap没有桶的概念,所有的元素都存储在一颗树中。

构造方法

/**

  • 默认构造方法,比较器为null,那么会使用key的比较器,也就意味着key必须实现Comparable 接口,否则在比较的时候就会出现异常

*/

public TreeMap() {

comparator = null;

}

/**

  • 使用传入的comparator比较两个key的大小

*/

public TreeMap(Comparator<? super K> comparator) {

this.comparator = comparator;

}

/**

  • key必须实现Comparable接口,把传入map中的所有元素保存到新的TreeMap中

*/

public TreeMap(Map<? extends K, ? extends V> m) {

comparator = null;

putAll(m);

}

/**

  • 使用传入map的比较器,并把传入map中的所有元素保存到新的TreeMap中

*/

public TreeMap(SortedMap<K, ? extends V> m) {

comparator = m.comparator();

try {

buildFromSorted(m.size(), m.entrySet().iterator(), null, null);

} catch (java.io.IOException cannotHappen) {

} catch (ClassNotFoundException cannotHappen) {

}

}

TreeMap构造方法主要分成两类,一类是使用comparator比较器,一类是key必须实现Comparable接口。构造方法中比较器的值为null,采用自然排序的方法,如果指定了比较器则称之为定制排序

  • 自然排序:TreeMap的所有key必须实现Comparable接口,所有的key都是同一个类的对象

  • 定制排序:创建TreeMap对象传入了一个Comparator对象,该对象负责对TreeMap中所有的key进行排序,采用定制排序不要求Map的key实现Comparable接口。等下面分析到比较方法的时候在分析这两种比较有何不同。

数据结构

也叫红黑树结构 不了解树的可以去复习复习数据结构尚硅谷韩顺平讲数据结构

以及后面的二叉排序树与其删除

static final class Entry<K,V> implements Map.Entry<K,V> {

/**

*/

K key;

/**

*/

V value;

/**

  • 左结点

*/

Entry<K,V> left;

/**

  • 右结点

*/

Entry<K,V> right;

/**

  • 父结点

*/

Entry<K,V> parent;

/**

  • 结点颜色 默认为黑 只有两种颜色,红色和黑色

*/

boolean color = BLACK;

}

对于Map来说,使用的最多的就是put()/get()/remove()等方法,下面依次进行分析

put()

public V put(K key, V value) {

Entry<K,V> t = root;

//如果根结点为null,还没建立

if (t == null) {

//官方给的是:类型(可能为null)检查

compare(key, key); // type (and possibly null) check

//制造一个根结点,默认为黑

root = new Entry<>(key, value, null);

size = 1;

modCount++;

return null;

}

//定义一个cmp,这个变量用来进行二分查找时的比较

int cmp;

Entry<K,V> parent;

// split comparator and comparable paths 拆分比较器和可比较路径

//也就是 cpr表示有无自己定义的排序规则,分两种情况遍历执行,主要目的就是找到要插入结点的父结点

Comparator<? super K> cpr = comparator;

if (cpr != null) {

do {

//存取要插入结点的父结点

parent = t;

//比较新的key与根结点key的大小,相当于维护了二叉排序树

cmp = cpr.compare(key, t.key);

//小的就放左边

if (cmp < 0)

t = t.left;

//大的就放右边

else if (cmp > 0)

t = t.right;

//一样就覆盖,并返回旧的值

else

return t.setValue(value);

} while (t != null);

}

//如果比较器为空,则使用key作为比较器进行比较

else {

//这里要求key不能为空,并且必须实现Comparable接口

if (key == null)

throw new NullPointerException();

@SuppressWarnings(“unchecked”)

//类型转换,也就相当于实现Comparable接口

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

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;

}

compare

//比较方法,如果comparator==null ,采用comparable.compartTo进行比较,否则采用指定比较器比较大小

final int compare(Object k1, Object k2) {

return comparator==null ? ((Comparable<? super K>)k1).compareTo((K)k2)
comparator.compare((K)k1, (K)k2);

}

除去调整红黑树的部分,其余的都很简单,就是搜索二叉树的插入过程

平衡红黑树

fixAfterInsertion

private void fixAfterInsertion(Entry<K,V> x) {

//插入结点的颜色默认是红色

x.color = RED;

//非空,非根结点,父结点为红结点,否则不用操作

while (x != null && x != root && x.parent.color == RED) {

//判断x父结点是否是x爷爷结点的左节结点

if (parentOf(x) == leftOf(parentOf(parentOf(x)))) {

//找到爷爷结点的右结点给到y

Entry<K,V> y = rightOf(parentOf(parentOf(x)));

//如果x的父结点的兄弟结点y是红色,则x的父结点肯定也是红色的

if (colorOf(y) == RED) {

//父结点和叔结节点都为红色,此时通过变色即可实现平衡

//x的父结点设置为黑色

setColor(parentOf(x), BLACK);

//x的父结点的兄弟结点y也设置成黑色

setColor(y, BLACK);

//x的爷爷结点设置为红色

setColor(parentOf(parentOf(x)), RED);

//将x的爷爷结点重置给x

x = parentOf(parentOf(x));

//如果x的父结点和叔父结点是黑色

} else {

//如果x是父结点的右结点

if (x == rightOf(parentOf(x))) {

//将x的父结点重置给x

x = parentOf(x);

//然后左旋

rotateLeft(x);

}

//设置x的父结点为黑色

setColor(parentOf(x), BLACK);

//设置x的爷爷结点为红色

setColor(parentOf(parentOf(x)), RED);

//将x的爷爷结点右旋

rotateRight(parentOf(parentOf(x)));

}

//x父结点是x爷爷结点的右节结点

} else {

//找到爷爷结点的左结点给到y

Entry<K,V> y = leftOf(parentOf(parentOf(x)));

//如果父结点和叔结节点都为红色,此时通过变色即可实现平衡 和上面一样

if (colorOf(y) == RED) {

setColor(parentOf(x), BLACK);

setColor(y, BLACK);

setColor(parentOf(parentOf(x)), RED);

x = parentOf(parentOf(x));

//都为黑

} else {

//如果x是父结点的左结点

if (x == leftOf(parentOf(x))) {

//将父结点重置给x

x = parentOf(x);

//右旋x

rotateRight(x);

}

//设置x的父结点为黑色

setColor(parentOf(x), BLACK);

//设置x的爷爷结点为红色

setColor(parentOf(parentOf(x)), RED);

//左旋爷爷结点

rotateLeft(parentOf(parentOf(x)));

}

}

}

//根结点一定是黑色

root.color = BLACK;

}

  • 设置颜色左右旋的目的就是保证红黑树的规则

红黑树是一个更高效的检索二叉树,有如下特点:

  1. 每个节点只能是红色或者黑色

  2. 根节点永远是黑色的

  3. 每个叶子节点(NIL)是黑色。(注意:这里叶子节点,是指为空(NIL或NULL)的叶子节点!)

  4. 如果一个节点是红色的,则它的子节点必须是黑色的。

  5. 从一个节点到该节点的子孙节点的所有路径上包含相同数目的黑节点

左旋

在这里插入图片描述

  • 其实就是 P结点为树的结构改变,将P结点的右结点R作为父结点,将R的左结点给到P结点的右结点,将P结点给到R结点的左结点,重新变成以R结点为树节点的二叉树

最后我们该如何学习?

1、看视频进行系统学习

这几年的Crud经历,让我明白自己真的算是菜鸡中的战斗机,也正因为Crud,导致自己技术比较零散,也不够深入不够系统,所以重新进行学习是很有必要的。我差的是系统知识,差的结构框架和思路,所以通过视频来学习,效果更好,也更全面。关于视频学习,个人可以推荐去B站进行学习,B站上有很多学习视频,唯一的缺点就是免费的容易过时。

另外,我自己也珍藏了好几套视频资料躺在网盘里,有需要的我也可以分享给你:

1年半经验,2本学历,Curd背景,竟给30K,我的美团Offer终于来了

2、读源码,看实战笔记,学习大神思路

“编程语言是程序员的表达的方式,而架构是程序员对世界的认知”。所以,程序员要想快速认知并学习架构,读源码是必不可少的。阅读源码,是解决问题 + 理解事物,更重要的:看到源码背后的想法;程序员说:读万行源码,行万种实践。

Spring源码深度解析:

1年半经验,2本学历,Curd背景,竟给30K,我的美团Offer终于来了

Mybatis 3源码深度解析:

1年半经验,2本学历,Curd背景,竟给30K,我的美团Offer终于来了

Redis学习笔记:

1年半经验,2本学历,Curd背景,竟给30K,我的美团Offer终于来了

Spring Boot核心技术-笔记:

1年半经验,2本学历,Curd背景,竟给30K,我的美团Offer终于来了

3、面试前夕,刷题冲刺

面试的前一周时间内,就可以开始刷题冲刺了。请记住,刷题的时候,技术的优先,算法的看些基本的,比如排序等即可,而智力题,除非是校招,否则一般不怎么会问。

关于面试刷题,我个人也准备了一套系统的面试题,帮助你举一反三:

1年半经验,2本学历,Curd背景,竟给30K,我的美团Offer终于来了

只有技术过硬,在哪儿都不愁就业,“万般带不去,唯有业随身”学习本来就不是在课堂那几年说了算,而是在人生的旅途中不间断的事情。

人生短暂,别稀里糊涂的活一辈子,不要将就。
3)]

2、读源码,看实战笔记,学习大神思路

“编程语言是程序员的表达的方式,而架构是程序员对世界的认知”。所以,程序员要想快速认知并学习架构,读源码是必不可少的。阅读源码,是解决问题 + 理解事物,更重要的:看到源码背后的想法;程序员说:读万行源码,行万种实践。

Spring源码深度解析:

[外链图片转存中…(img-FHF57NBT-1720081837564)]

Mybatis 3源码深度解析:

[外链图片转存中…(img-i3Oi4bAr-1720081837564)]

Redis学习笔记:

[外链图片转存中…(img-9uPVJXJd-1720081837565)]

Spring Boot核心技术-笔记:

[外链图片转存中…(img-VymrCs5L-1720081837565)]

3、面试前夕,刷题冲刺

面试的前一周时间内,就可以开始刷题冲刺了。请记住,刷题的时候,技术的优先,算法的看些基本的,比如排序等即可,而智力题,除非是校招,否则一般不怎么会问。

关于面试刷题,我个人也准备了一套系统的面试题,帮助你举一反三:

[外链图片转存中…(img-c9ZBBblu-1720081837566)]

只有技术过硬,在哪儿都不愁就业,“万般带不去,唯有业随身”学习本来就不是在课堂那几年说了算,而是在人生的旅途中不间断的事情。

人生短暂,别稀里糊涂的活一辈子,不要将就。

  • 5
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值