一、初识TreeMap
先看一下TreeMap的类图设计:
- TreeMap 是一个 有序的key-value集合,它是通过 红黑树 实现的。
- TreeMap 继承于AbstractMap,所以它是一个Map,即一个key-value集合。
- TreeMap 实现了NavigableMap接口,意味着它支持一系列的导航方法,比如返回有序的key集合。
- TreeMap 实现了Cloneable接口,意味着它能被克隆。
- TreeMap 实现了java.io.Serializable接口,意味着它支持序列化。
TreeMap是基于红黑树实现的,红黑树是一种特殊的二叉排序树,红黑树通过一些限制,使其不会出现二叉树排序树中极端的一边倒的情况,相对二叉排序树而言,这自然提高了查询的效率。
红黑树的基本性质如下:
- 每个节点都只能是红色或者黑色
- 根节点是黑色
- 每个叶节点(NIL节点,空节点)是黑色的
- 如果一个结点是红的,则它两个子节点都是黑的,也就是说在一条路径上不能出现相邻的两个红色结点
- 从任一节点到其每个叶子的所有路径都包含相同数目的黑色节点。
TreeMap排序默认是升序的,数字比较大小,字符串比较首字母,其他类型则需要自己实现Comparable接口,否则排序时会报错。
二、源码解析
1、成员变量
// key的比较器
private final Comparator<? super K> comparator;
// 根节点
private transient TreeMapEntry<K,V> root;
// 节点数量
private transient int size = 0;
// 修改的次数, fail-fast机制
private transient int modCount = 0;
// 键值对集合
private transient EntrySet entrySet;
// 键集合
private transient KeySet<K> navigableKeySet;
// 降序的NavigableMap
private transient NavigableMap<K,V> descendingMap;
2、构造函数
// 无参构造函数
public TreeMap() {
comparator = null;
}
// 参数为比较器的构造函数
public TreeMap(Comparator<? super K> comparator) {
this.comparator = comparator;
}
// 参数为Map的构造函数
public TreeMap(Map<? extends K, ? extends V> m) {
comparator = null;
putAll(m);
}
// 参数为SortedMap的构造函数
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) {
}
}
3、put方法
public V put(K key, V value) {
Entry<K,V> t = root;
// 若红黑树为空,则插入根节点
if (t == null) {
compare(key, key);
root = new Entry<K,V>(key, value, null);
size = 1;
modCount++;
return null;
}
int cmp;
Entry<K,V> parent;
// 比较器
Comparator<? super K> cpr = comparator;
// 红黑树是以key来进行排序的,以key来进行查找在二叉排序树中的插入位置。
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
return t.setValue(value);
} while (t != null);
}
else {
if (key == null)
throw new NullPointerException();
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<K,V>(key, value, parent);
if (cmp < 0)
parent.left = e;
else
parent.right = e;
// 插入新的节点后,调用fixAfterInsertion调整红黑树。
fixAfterInsertion(e);
size++;
modCount++;
return null;
}
/** 新增节点后对红黑树的调整 */
private void fixAfterInsertion(Entry<K,V> x) {
// 将新插入节点的颜色设置为红色
x. color = RED;
// while循环,新插入节点x是根节点或者新插入节点x的父节点是红色这两种情况不需要调整
while (x != null && x != root && x. parent.color == RED) {
// 如果新插入节点x的父节点是祖父节点的左孩子
if (parentOf(x) == leftOf(parentOf (parentOf(x)))) {
// 取得新插入节点x的叔叔节点
Entry<K,V> y = rightOf(parentOf (parentOf(x)));
// 如果新插入x的父节点是红色-------------------①
if (colorOf(y) == RED) {
// 将x的父节点设置为黑色
setColor(parentOf (x), BLACK);
// 将x的叔叔节点设置为黑色
setColor(y, BLACK);
// 将x的祖父节点设置为红色
setColor(parentOf (parentOf(x)), RED);
// 将x指向祖父节点
x = parentOf(parentOf (x));
} else {
// 如果新插入x的叔叔节点是黑色或缺少,且x的父节点是祖父节点的右孩子
if (x == rightOf( parentOf(x))) {
// 左旋父节点
x = parentOf(x);
rotateLeft(x);
}
// 如果新插入x的叔叔节点是黑色或缺少,且x的父节点是祖父节点的左孩子
// 将x的父节点设置为黑色
setColor(parentOf (x), BLACK);
// 将x的祖父节点设置为红色
setColor(parentOf (parentOf(x)), RED);
// 右旋x的祖父节点
rotateRight( parentOf(parentOf (x)));
}
} else {
// 如果新插入节点x的父节点是祖父节点的右孩子
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 {
if (x == leftOf( parentOf(x))) {
x = parentOf(x);
rotateRight(x);
}
setColor(parentOf (x), BLACK);
setColor(parentOf (parentOf(x)), RED);
rotateLeft( parentOf(parentOf (x)));
}
}
}
// 最后将根节点设置为黑色
root.color = BLACK;
}
/**
* 对红黑树的节点(x)进行左旋转
*
* 左旋示意图(对节点x进行左旋):
* px px
* / /
* x y
* / \ --(左旋)-- / \
* lx y x ry
* / \ / \
* ly ry lx ly
*
*/
private void rotateLeft(Entry<K,V> p) {
if (p != null) {
// 取得要选择节点p的右孩子
Entry<K,V> r = p. right;
// p的右孩子 = r的左孩子
p. right = r.left ;
// r的左孩子非空,p = r的左孩子的父亲
if (r.left != null)
r.left.parent = p;
// 将p的父亲设为y的父亲
r. parent = p.parent ;
// 如果p的父亲是空节点,则将r设为根节点
if (p.parent == null)
root = r;
// 如果p是它父节点的左孩子,则p的父节点的左孩子 = r
else if (p.parent. left == p)
p. parent.left = r;
else
// 如果p是它父节点的左孩子,p的父节点的左孩子 = r
p. parent.right = r;
// r的左孩子 = p
r. left = p;
// p的父节点 = r
p. parent = r;
}
}
/**
* 对红黑树的节点进行右旋转
*
* 右旋示意图(对节点y进行右旋):
* py py
* / /
* y x
* / \ --(右旋)-- / \
* x ry lx y
* / \ / \
* lx rx rx ry
*
*/
private void rotateRight(Entry<K,V> p) {
if (p != null) {
// 取得要选择节点p的左孩子
Entry<K,V> l = p. left;
// 将"l的右孩子"设为"p的左孩子"
p. left = l.right ;
// 如果"l的右孩子"不为空的话,将"p"设为"l的右孩子的父亲"
if (l.right != null) l. right.parent = p;
// 将"p的父亲"设为"l的父亲"
l. parent = p.parent ;
// 如果"p的父亲"是空节点,则将l设为根节点
if (p.parent == null)
root = l;
// 如果p是它父节点的右孩子,则将l设为"p的父节点的右孩子"
else if (p.parent. right == p)
p. parent.right = l;
//如果p是它父节点的左孩子,将l设为"p的父节点的左孩子"
else p.parent .left = l;
// 将"p"设为"l的右孩子"
l. right = p;
// 将"l"设为"p父节点"
p. parent = l;
}
}
- 如果树为null,则构建一个TreeMapEntry设置为当前的root.
- 检查之前TreeMap的comparator是否为空,不为空则用comparator去对比key,否则用k.compareTo(t.key)比较,然后遍历当前树,找到对应- 的key则修改对应的value。
- 如果遍历树没找到,则通过new TreeMapEntry<>(key, value, parent); 添加到树上,然后执行fixAfterInsertion(e)保证root还是一颗红黑树。
4、remove方法
public V remove(Object key) {
// 根据key查找到对应的节点对象
Entry<K,V> p = getEntry(key);
if (p == null)
return null;
// 记录key对应的value
V oldValue = p.value;
// 删除节点
deleteEntry(p);
return oldValue;
}
private void deleteEntry(Entry<K,V> p) {
modCount++;
// map容器的元素个数减一
size--;
// 如果被删除的节点p的左孩子和右孩子都不为空,则查找其替代节点
if (p.left != null && p. right != null) {
// 查找p的替代节点
Entry<K,V> s = successor (p);
p. key = s.key ;
p. value = s.value ;
// 将p指向替代节点
p = s;
}
// replacement为替代节点p的继承者,p的左孩子存在则用p的左孩子替代,否则用p的右孩子
Entry<K,V> replacement = (p. left != null ? p.left : p. right);
if (replacement != null) {
// 将p的父节点拷贝给替代节点
replacement. parent = p.parent ;
// 如果替代节点p的父节点为空,也就是p为跟节点,则将replacement设置为根节点
if (p.parent == null)
root = replacement;
// 如果替代节点p是其父节点的左孩子,则将replacement设置为其父节点的左孩子
else if (p == p.parent. left)
p. parent.left = replacement;
// 如果替代节点p是其父节点的左孩子,则将replacement设置为其父节点的右孩子
else
p. parent.right = replacement;
// 将替代节点p的left、right、parent的指针都指向空,即解除前后引用关系(相当于将p从树种摘除),使得gc可以回收
p. left = p.right = p.parent = null;
// Fix replacement
// 如果替代节点p的颜色是黑色,则需要调整红黑树以保持其平衡
if (p.color == BLACK)
fixAfterDeletion(replacement);
} else if (p.parent == null) {
// 如果要替代节点p没有父节点,代表p为根节点,直接删除即可
root = null;
} else {
// 判断进入这里说明替代节点p没有孩子
// 如果p的颜色是黑色,则调整红黑树
if (p.color == BLACK)
fixAfterDeletion(p);
// 下面删除替代节点p
if (p.parent != null) {
// 解除p的父节点对p的引用
if (p == p.parent .left)
p. parent.left = null;
else if (p == p.parent. right)
p. parent.right = null;
// 解除p对p父节点的引用
p. parent = null;
}
}
}
/**
* 查找要删除节点的替代节点
*/
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;
}
}
private void fixAfterDeletion(Entry<K,V> x) {
// while循环,保证要删除节点x不是跟节点,并且是黑色
while (x != root && colorOf (x) == BLACK) {
// 如果要删除节点x是其父亲的左孩子
if (x == leftOf( parentOf(x))) {
// 取出要删除节点x的兄弟节点
Entry<K,V> sib = rightOf(parentOf (x));
// 如果删除节点x的兄弟节点是红色
if (colorOf(sib) == RED) {
// 将x的兄弟节点颜色设置为黑色
setColor(sib, BLACK);
// 将x的父节点颜色设置为红色
setColor(parentOf (x), RED);
// 左旋x的父节点
rotateLeft( parentOf(x));
// 将sib重新指向旋转后x的兄弟节点 ,进入else的步奏
sib = rightOf(parentOf (x));
}
// 如果x的兄弟节点的两个孩子都是黑色
if (colorOf(leftOf(sib)) == BLACK &&
colorOf(rightOf (sib)) == BLACK) {
// 将兄弟节点的颜色设置为红色
setColor(sib, RED);
// 将x的父节点指向x,如果x的父节点是黑色,需要将x的父节点整天看做一个节点继续调整
x = parentOf(x);
} else {
// 如果x的兄弟节点右孩子是黑色,左孩子是红
if (colorOf(rightOf(sib)) == BLACK) {
// 将x的兄弟节点的左孩子设置为黑色
setColor(leftOf (sib), BLACK);
// 将x的兄弟节点设置为红色
setColor(sib, RED);
// 右旋x的兄弟节点
rotateRight(sib);
// 将sib重新指向旋转后x的兄弟节点
sib = rightOf(parentOf (x));
}
// 如果x的兄弟节点右孩子是红色
setColor(sib, colorOf (parentOf(x)));
// 将x的父节点设置为黑色
setColor(parentOf (x), BLACK);
// 将x的兄弟节点的右孩子设置为黑色
setColor(rightOf (sib), BLACK);
// 左旋x的父节点
rotateLeft( parentOf(x));
// 达到平衡,将x指向root,退出循环
x = root;
}
} else {
// 如果要删除节点x是其父亲的右孩子
Entry<K,V> sib = leftOf(parentOf (x));
if (colorOf(sib) == RED) {
setColor(sib, BLACK);
setColor(parentOf (x), RED);
rotateRight( parentOf(x));
sib = leftOf(parentOf (x));
}
if (colorOf(rightOf(sib)) == BLACK &&
colorOf(leftOf (sib)) == BLACK) {
setColor(sib, RED);
x = parentOf(x);
} else {
if (colorOf(leftOf(sib)) == BLACK) {
setColor(rightOf (sib), BLACK);
setColor(sib, RED);
rotateLeft(sib);
sib = leftOf(parentOf (x));
}
setColor(sib, colorOf (parentOf(x)));
setColor(parentOf (x), BLACK);
setColor(leftOf (sib), BLACK);
rotateRight( parentOf(x));
x = root;
}
}
}
setColor(x, BLACK);
}
从红黑树中删除一个节点,需要调用fixAfterDeletion方法修复红黑树
5、get方法
public V get(Object key) {
Entry<K,V> p = getEntry(key);
return (p==null ? null : p. value);
}
final Entry<K,V> getEntry(Object key) {
if (comparator != null)
// 如果比较器为空,用key作为比较器查询
return getEntryUsingComparator(key);
if (key == null)
throw new NullPointerException();
Comparable<? super K> k = (Comparable<? super K>) key;
// 取得root节点
Entry<K,V> p = root;
// 从root节点开始查找,根据比较器判断是在左子树还是右子树
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;
}
final Entry<K,V> getEntryUsingComparator(Object key) {
K k = (K) key;
Comparator<? super K> cpr = comparator ;
if (cpr != null) {
Entry<K,V> p = root;
while (p != null) {
int cmp = cpr.compare(k, p.key );
if (cmp < 0)
p = p. left;
else if (cmp > 0)
p = p. right;
else
return p;
}
}
return null;
}
利用排序二叉树的特征来搜索key值对应的Entry,从二叉树的根节点开始,如果被搜索节点大于当前节点,程序向右子树搜索;如果被搜索节点小于当前节点,程序向左子树搜索;如果相等,那就是找到了指定节点。
三、总结
TreeMap内部用红黑树保存数据,迭代顺序按照key值有序,与HashMap相比效率更低,只建议在需要按序索引key值时使用,它也是非线程安全的,key和value均不能为null值。