前言
上一章的HashMap并没有提到红黑树,就是因为本章的TreeMap就是一棵红黑树。TreeMap是存储键值对(key-value结构)的自平衡二叉树,又称红黑树。TreeMap的key是有序且不可为空的,但是value是可以为空的。TreeMap的类图结构如下
TreeMap类上的注释有两个地方需要注意:
1.TreeMap是一个基于NavigableMap实现的红黑树,TreeMap的排序方式有两种:(1)基于key的自然排序(2)在创建TreeMap的时候提供一个比较器,使用哪种排序取决于你使用的哪种构造方法,也就是默认无参构造方法基于自然排序,如果key是自定义对象,要么你的自定义对象是已经实现了Comparable接口或在创建TreeMap时提供一个Comparator的实现
/**A Red-Black tree based {@link NavigableMap} implementation.
* The map is sorted according to the {@linkplain Comparable natural
* ordering} of its keys, or by a {@link Comparator} provided at map
* creation time, depending on which constructor is used.
*/
2.TreeMap的实现不是同步的,其实也就是多线程不安全的。如果多线程同时访问一个映射,并且进行了结构性的修改,那么它必须时同步的(结构性修改是指添加或者删除一个或多个映射,修改已经存在的key的值不算是结构性修改)。并且注释给出的多线程安全使用TreeMap的方式是SortedMap m = Collections.synchronizedSortedMap(new TreeMap(…));
/**<p><strong>Note that this implementation is not synchronized.</strong>
* If multiple threads access a map concurrently, and at least one of the
* threads modifies the map structurally, it <em>must</em> be synchronized
* externally. (A structural modification is any operation that adds or
* deletes one or more mappings; merely changing the value associated
* with an existing key is not a structural modification.) This is
* typically accomplished by synchronizing on some object that naturally
* encapsulates the map.
*/
红黑树TreeMap
红黑树的定义
首先红黑树是一棵特殊的二叉查找树,二叉查找树的每个节点键值大于左孩子的键值,小于或等于右孩子的键值;二叉查找树在特殊情况下所有子节点都在左边或者右边,这被称为二叉查找树的左倾或右倾,极端情况下,二叉查找树就变成了一个有序的链表了,而红黑树呢通过对二叉查找树的着色,将二叉查找树变成了一棵相对平衡的树,不会出现左倾或右倾的情况:
(1) 每个节点或者是黑色,或者是红色。
(2) 根节点是黑色。
(3) 每个叶子节点是黑色。 [注意:这里叶子节点,是指为空的叶子节点!]
(4) 如果一个节点是红色的,则它的子节点必须是黑色的。
(5) 从一个节点到该节点的子孙节点的所有路径上包含相同数目的黑节点。
TreeMap中红黑树的节点元素包含自身的键值对,左右孩子节点,父节点,节点默认黑色满足条件3
TreeMap如何添加元素
TreeMap的put方法
public V put(K key, V value) {
// 取类对象实例的根节点
Entry<K,V> t = root;
// 若根节点为空
if (t == null) {
// 调用比较方法,看看当前key值是否可比较,不可比较抛出ClassCastException,key为空抛出NullPointerException异常
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;
// 必须有比较器,这里这个key的非空和比较校验其实可以提前,毕竟下面寻找节点位置设置值的代码重复了,没必要
if (cpr != null) {
/**
* 从根节点循环遍历比较key的值
* 1.若插入的key的值比当前节点的key值小,取当前节点的左子节点
* 2.若插入的key的值比当前节点的key值大,取当前节点的右子节点
* 3.若插入的key的值等于当前节点的key的值,则设置值覆盖之前的值
* 4.若遍历的节点为空,则结束遍历
*/
do {
parent = t;
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);
}
else {
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
return t.setValue(value);
} while (t != null);
}
// 遍历没找到节点,则新建一个节点
Entry<K,V> e = new Entry<>(key, value, parent);
// 如果key小于遍历节点的key值
if (cmp < 0)
// 将该节点的左子节点设置为新建节点
parent.left = e;
// 如果key大于遍历节点的key值
else
// 将该节点的右子节点设置为新建节点
parent.right = e;
// 插入节点完成之后进行着色
fixAfterInsertion(e);
size++;
modCount++;
return null;
}
从以上代码来看,TreeMap的put方法的前一部分跟二叉查找树的新增节点没有区别,先按照节点的key的值的顺序,寻找节点的位置,然后创建节点,改变父节点的孩子节点的引用,然后完成这个动作之后,再给节点着色
private void fixAfterInsertion(Entry<K,V> x) {
// 默认当前插入节点的颜色为红色
x.color = RED;