概述
- TreeMap 是一个有序的key-value集合,它是按照提供的比较器进行比较,没有添加则使用默认比较器,注意这里的有序不是按照添加的顺序。
- TreeMap 继承于AbstractMap,所以它是一个Map,即一个key-value集合。
- TreeMap 实现了Cloneable接口,它能被克隆。实现了java.io.Serializable接口,它支持序列化。
- TreeMap基于红黑树实现, containsKey、get、put 和 remove 的时间复杂度是 log(n) 。
- TreeMap是线程不安全的。 它的iterator 方法返回的迭代器支持fail-fastl机制。
数据结构
TreeMap底层使用红黑树存储节点数据,红黑树本质是一颗二叉查找树,简单介绍一下二叉查找树。
二叉查找树
二叉查找树(Binary Search Tree),也称有序二叉树(ordered binary tree),排序二叉树(sorted binary tree),是指一棵空树或者具有下列性质的二叉树
- 若任意结点的左子树不空,则左子树上所有结点的值均小于它的根结点的值;
- 若任意结点的右子树不空,则右子树上所有结点的值均大于它的根结点的值;
- 任意结点的左、右子树也分别为二叉查找树。
- 没有键值相等的结点(no duplicate nodes)。
因为,一棵由n个结点,随机构造的二叉查找树的高度为lgn,所以顺理成章,一般操作的执行时间为O(logn).。
但二叉树若退化成了一棵具有n个结点的线性链后,则此些操作最坏情况运行时间为O(n)。后面我们会看到一种基于二叉查找树-红黑树,它通过一些性质使得树相对平衡,使得最终查找、插入、删除的时间复杂度最坏情况下依然为O(logn)。
红黑树
红黑树在二叉查找树的基础上添加了着色和5条相关限制,从而保证了红黑树的增删改查最坏情况下的复杂度为O(logn)。
红黑树的性质:
1)每个结点要么是红的,要么是黑的。
2)根结点是黑的。
3)每个叶结点(叶结点即指树尾端NIL指针或NULL结点)是黑的。
4)如果一个结点是红的,那么它的俩个儿子都是黑的。
5)对于任一结点而言,其到叶结点树尾端NIL指针的每一条路径都包含相同数目的黑结点。
入下图所示就是一颗红黑树下图引自(wikipedia:http://t.cn/hgvH1l)
函数源码解析
构造函数
//默认空参,无比较器
public TreeMap() {
comparator = null;
}
//提供比较器的构造函数
public TreeMap(Comparator<? super K> comparator) {
this.comparator = comparator;
}
//创建一个包含了map的TreeMap
public TreeMap(Map<? extends K, ? extends V> m) {
comparator = null;
putAll(m);
}
左旋/右旋
在红黑树插入或者删除节点后可能会破坏它的性质,通常需要修改着色和左右旋转操作来恢复红黑树的特性。
左旋:
图片来自:https://www.cnblogs.com/skywang12345/p/3310928.html
如图,将节点X按照X和Y的连线为轴进行左旋,使X成为了Y的左孩子,同时Y的左孩子β,称为了X的右孩子。
步骤大致为:
- 将Y的左子节点β设置为X的右子节点
- 将X的父节点设置为Y
- 将Y的父节点设置成X的父节点,若X的父节点为空则设置Y为根节点。
代码实现:(注释按照上图示例)
//假设传入的p是上图中的X
private void rotateLeft(Entry<K,V> p) {
if (p != null) {
//指针r指向p的右孩子。(X的右孩子Y)
Entry<K,V> r = p.right;
//p的右孩子设置为r的左孩子。(X的右孩子设置成β)
p.right = r.left;
if (r.left != null){
//如果r的做孩子不为空,将父节点设置为p。(β父节点设置为X)
r.left.parent = p;
}
//r的父节点设置为p的父节点。(Y的父节点设置为X的父节点)
r.parent = p.parent;
if (p.parent == null)
//p的父节点为空则表示它的根节点,将r设置成根节点
root = r;
else if (p.parent.left == p)
//p是父节点的左子节点则将r设置成左子节点
p.parent.left = r;
else
//同理设置成右子节点
p.parent.right = r;
//r的左节点设置成p
r.left = p;
//p的父节点设置成r
p.parent = r;
}
}
右旋
图片来自:https://www.cnblogs.com/skywang12345/p/3310928.html
如图所示,将节点Y按照X和Y的连接线为轴进行右旋,称为了X的 右子节点,同时X的右子节点β称为了Y的左子节点。和左旋差别不大,不做详细介绍
private void rotateRight(Entry<K,V> p) {
if (p != null) {
Entry<K,V> l = p.left;
p.left = l.right;
if (l.right != null) l.right.parent = p;
l.parent = p.parent;
if (p.parent == null)
root = l;
else if (p.parent.right == p)
p.parent.right = l;
else p.parent.left = l;
l.right = p;
p.parent = l;
}
}
put()
向TreeMap中添加节点本质上是在红黑树中添加节点。
public V put(K key, V value) {
//获得根节点
Entry<K,V> t = root;
//根节点为空时,将添加的节点设置为根节点。
if (t == null) {
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;
Comparator<? super K> cpr = comparator;
//如果有提供比较器
if (cpr != null)