文章目录
1. 概述
TreeMap ,默认按照 key 来排序的 Map 实现类,也可以重写comparator方法来根据value进行排序。
采用红黑树实现 红黑树具有以下的特性
- 有序性:红黑树是一种二叉平衡查找树,父节点的 key 小于左子节点的 key ,大于右子节点的 key 。这样,就完成了 TreeMap 的有序的特性。
- 高性能:红黑树会进行自平衡,避免树的高度过高,导致查找性能下滑。这样,红黑树能够提供
logN
的时间复杂度。
红黑树的性质:
- 性质1:每个节点要么是黑色,要么是红色。
- 性质2:根节点是黑色。
- 性质3:每个叶子节点(NIL)是黑色。
- 性质4:每个红色结点的两个子结点一定都是黑色。
- 性质5:任意一结点到每个叶子结点的路径都包含数量相同的黑
2. 类图
TreeMap 实现的接口、继承的类,如下图所示:
- 继承
java.util.AbstractMap
抽像类。 - 实现 java.util.NavigableMap 接口
- 实现 序列化 接口。
- 实现 克隆接口。
3. 属性
private final Comparator<? super K> comparator;// key 排序器
private transient Entry<K,V> root;//红黑树的根节点
private transient int size = 0;//key-value 键值对数量
private transient int modCount = 0;//修改次数
4. 构造方法
TreeMap 一共有四个构造方法
① #TreeMap()
public TreeMap() {
comparator = null;
}
- 默认构造方法,不使用自定义排序,所以此时
comparator
为空。
② #TreeMap(Comparator<? super K> comparator)
public TreeMap(Comparator<? super K> comparator) {
this.comparator = comparator;
}
- 可传入
comparator
参数,自定义 key 的排序规则。
③ #TreeMap(SortedMap<K, ? extends V> m)
**
public TreeMap(SortedMap<K, ? extends V> m) {
// 设置 comparator 属性
comparator = m.comparator();
try {
// 使用 m 构造红黑树
buildFromSorted(m.size(), m.entrySet().iterator(), null, null);
} catch (java.io.IOException | ClassNotFoundException cannotHappen) {
}
}
- 设置比较器为m的比较器。
- 然后调用buildFromSorted方法构建TreeMap。代码如下:
private void buildFromSorted(int size, Iterator<?> it,
java.io.ObjectInputStream str,
V defaultVal)
throws java.io.IOException, ClassNotFoundException {
// 设置 key-value 键值对的数量
this.size = size;
// computeRedLevel(size) 方法,计算红黑树的高度
// 使用 m 构造红黑树,返回根节点
root = buildFromSorted(0, 0, size - 1, computeRedLevel(size),
it, str, defaultVal);
}
-
设置 key-value 键值对的数量到
size
。 -
调用
#computeRedLevel(int size)
方法,计算红黑树的高度。代码如下:private static int computeRedLevel(int size) { return 31 - Integer.numberOfLeadingZeros(size + 1); }
-
调用
#buildFromSorted(int level, int lo, int hi, int redLevel, Iterator<?> it, ObjectInputStream str, V defaultVal)
方法,使用m
构造红黑树,返回根节点。代码如下:
private final Entry<K,V> buildFromSorted(int level, int lo, int hi,
int redLevel,
Iterator<?> it,
java.io.ObjectInputStream str,
V defaultVal)
throws java.io.IOException, ClassNotFoundException {
/*
* Strategy: The root is the middlemost element. To get to it, we
* have to first recursively construct the entire left subtree,
* so as to grab all of its elements. We can then proceed with right
* subtree.
*
* The lo and hi arguments are the minimum and maximum
* indices to pull out of the iterator or stream for current subtree.
* They are not actually indexed, we just proceed sequentially,
* ensuring that items are extracted in corresponding order.
*/
// 递归结束
if (hi < lo) return null;
// 计算中间值
int mid = (lo + hi) >>> 1;
// <2.1> 创建左子树
Entry<K,V> left = null;
if (lo < mid)
// 递归左子树
left = buildFromSorted(level + 1, lo, mid - 1, redLevel,
it, str, defaultVal);
// 获得 key-value 键值对
K key;
V value;
if (it != null) { // 使用 it 迭代器,获得下一个值
if (defaultVal == null) {
Map.Entry<?,?> entry = (Map.Entry<?,?>)it.next(); // 从 it 获得下一个 Entry 节点
key = (K) entry.getKey(); // 读取 key
value = (V) entry.getValue(); // 读取 value
} else { // 默认值不为空
key = (K)it.next(); // 读取 key
value = defaultVal; // 设置 default 为 value
}
} else { // use stream 处理 str 流的情况
key = (K) str.readObject(); // 从 str 读取 key 值
value = (defaultVal != null ? defaultVal : (V) str.readObject()); // 从 str 读取 value 值
}
// 创建中间节点
Entry<K,V> middle = new Entry<>(key, value, null);
// color nodes in non-full bottommost level red
// 如果到树的最大高度,则设置为红节点
if (level == redLevel)
middle.color = RED;
// 如果左子树不为空 设置左子树的父亲为mid mid的左子树是left
if (left != null) {
middle.left = left;
left.parent = middle;
}
// 创建右子树
if (mid < hi) {
// 递归右子树
Entry<K,V> right = buildFromSorted(level + 1, mid + 1, hi, redLevel,
it, str, defaultVal);
// 当前节点,设置右子树
middle.right = right;
// 右子树,设置父节点为当前节点
right.parent = middle;
}
// 返回当前节点
return middle;
}
④ #TreeMap(Map<? extends K, ? extends V> m)
// TreeMap.java
public TreeMap(Map<? extends K, ? extends V> m) {
comparator = null;
// 添加所有元素
putAll(m);
}
- 传入
m
的是 Map 类型,构建成初始的 TreeMap 。 - 调用
#putAll(Map<? extends K, ? extends V> map)
方法,添加所有元素。代码如下:
public void putAll(Map<? extends K, ? extends V> map) {
// 满足如下条件,调用 buildFromSorted 方法来优化处理
int mapSize = map.size();
if (size == 0 // 如果 TreeMap 的大小为 0
&& mapSize != 0 // map 的大小非 0
&& map instanceof SortedMap) { // 如果是 map 是 SortedMap 类型
if (Objects.equals(comparator, ((SortedMap<?,?>)map).comparator())) { // 排序规则相同
// 增加修改次数
++modCount;
// 基于 SortedMap 顺序迭代插入即可
try {
buildFromSorted(mapSize, map.entrySet().iterator(),
null, null);
} catch (java.io.IOException | ClassNotFoundException cannotHappen) {
}
return;
}
}
// 直接遍历 map 来添加
super.putAll(map);
}
5. 添加单个元素
#put(K key, V value)
方法,添加单个元素。代码如下:
public V put(K key, V value) {
// 记录当前根节点
Entry<K,V> t = root;
// //如果根结点为空 直接使用这个键值对作为根结点 然后返回。
if (t == null) {
// 校验 key 类型。
compare(key, key); // type (and possibly null) check
// 创建 Entry 节点
root = new Entry<>(key, value, null);
// 设置 key-value 键值对的数量
size = 1;
// 增加修改次数
modCount++;
return null;
}
// 开始遍历红黑树
int cmp;标记位 标记key比父结点小还是大
Entry<K,V> parent; // 父节点
// split comparator and comparable paths
Comparator<? super K> cpr = comparator;
if (cpr != null) {//不为空
do {
//记录下当前的父亲结点
parent = t;
//key和这个结点比较大小
cmp = cpr.compare(key, t.key);
//key比当前结点小。则遍历左子树
if (cmp < 0)
t = t.left;
//比当前结点大 遍历右子树
else if (cmp > 0)
t = t.right;
else
return t.setValue(value);// 一样 说明找到了位置。修改其值
} while (t != null);//循环
}
else { // 如果没有自定义 comparator ,则使用 key 自身比较器来比较
if (key == null) // 如果 key 为空,则抛出异常
throw new NullPointerException();
@SuppressWarnings("unchecked")
Comparable<? super K> k = (Comparable<? super K>) key;
do {
// 记录新的父节点
parent = t;
// 比较 key
cmp = k.compareTo(t.key);
// 比 key 小,说明要遍历左子树
if (cmp < 0)
t = t.left;
// 比 key 大,说明要遍历右子树
else if (cmp > 0)
t = t.right;
// 说明,相等,说明要找到的 t 就是 key 对应的节点,直接设置 value 即可。
else
return t.setValue(value);
} while (t != null);
}
// 创建 key-value 的 Entry 节点
Entry<K,V> e = new Entry<>(key, value, parent);
// 设置左右子树
if (cmp < 0)
parent.left = e;
else
parent.right = e;
// 插入后,进行自平衡
fixAfterInsertion(e);
// 设置 key-value 键值对的数量
size++;
// 增加修改次数
modCount++;
return null;
}
- 红黑树是一颗平衡树 所以可以根据二分查找的规则 使用定义的比较器来查找插入的位置
- 插入后红黑树需要自平衡处理。
6. 获得单个元素
#get(Object key)
方法,获得 key
对应的 value 值。代码如下:
// TreeMap.java
public V get(Object key) {
// 获得 key 对应的 Entry 节点
Entry<K,V> p = getEntry(key);
// 返回 value 值
return (p == null ? null : p.value);
}
final Entry<K,V> getEntry(Object key) { // 不使用 comparator 查找
// Offload comparator-based version for sake of performance
// 如果自定义了 comparator 比较器,则基于 comparator 比较来查找
if (comparator != null)
return getEntryUsingComparator(key);
// 如果 key 为空,抛出异常
if (key == null)
throw new NullPointerException();
@SuppressWarnings("unchecked")
Comparable<? super K> k = (Comparable<? super K>) key;
// 遍历红黑树
Entry<K,V> p = root;
while (p != null) {
// 比较值
int cmp = k.compareTo(p.key);
// 如果 key 小于当前节点,则遍历左子树
if (cmp < 0)
p = p.left;
// 如果 key 大于当前节点,则遍历右子树
else if (cmp > 0)
p = p.right;
// 如果 key 相等,则返回该节点
else
return p;
}
// 查找不到,返回 null
return null;
}
final Entry<K,V> getEntryUsingComparator(Object key) { // 使用 comparator 查找
@SuppressWarnings("unchecked")
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);
// 如果 key 小于当前节点,则遍历左子树
if (cmp < 0)
p = p.left;
// 如果 key 大于当前节点,则遍历右子树
else if (cmp > 0)
p = p.right;
// 如果 key 相等,则返回该节点
else
return p;
}
}
// 查找不到,返回 null
return null;
}
- 查找逻辑和添加逻辑很类似。不再赘述
7. 删除单个元素
相比 添加 来说,删除会更加复杂一些。所以呢,我们先看删除的四种情况。为了让案例更加复杂,我们会使用一颗二叉查找树来举例子。因为,在去掉自平衡的逻辑的情况下,红黑树的删除和二叉查查找树的删除逻辑是一致的。
对于二叉查找树的删除,需要保证删除节点后,能够继续满足二叉和查找的特性。
该图通过 http://btv.melezinek.cz/binary-search-tree.html 绘制
情况一,无子节点。
直接删除父节点对其的指向即可。
例如说,叶子节点 5、11、14、18 。
情况二,只有左子节点。
将删除节点的父节点,指向删除节点的左子节点。
例如说,节点 20 。可以通过将节点 15 的右子节点指向节点 19 。
情况三,只有右子节点。
和情况二的处理方式一致。将删除节点的父节点,指向删除节点的右子节点。
图中暂无示例,胖友自己脑补下,嘿嘿。
情况四,有左子节点 + 右子节点。
这种情况,相对会比较复杂,可以选择左子树中的最大值 或者右子树中的最小值来替代这个被删除的结点。
代码如下:
public V remove(Object key) {
// 获得 key 对应的 Entry 节点
Entry<K,V> p = getEntry(key);
// 如果不存在,则返回 null ,无需删除
if (p == null)
return null;
V oldValue = p.value;
// 删除节点
deleteEntry(p);
return oldValue;//返回旧值
}
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.
// 如果删除的节点 p 既有左子节点,又有右子节点,
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
// Start fixup at replacement node, if it exists.
// 左右孩子中不为空的那个作为替换结点
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)//表示删除的是根结点
root = replacement;//根结点设置成替换结点。
else if (p == p.parent.left)// 如果被删除结点是其父结点的左孩子 则父结点的做孩子设置成替换结点
p.parent.left = replacement;
else //否则相反
p.parent.right = replacement;
// Null out links so they are OK to use by fixAfterDeletion.
p.left = p.right = p.parent = null; // 置空
// Fix replacement
if (p.color == BLACK) // 如果p的颜色是黑色 执行自平衡
fixAfterDeletion(replacement);
} else if (p.parent == null) { // return if we are the only node.
root = null;
// 如果删除的没有左子树,又没有右子树
} else { // No children. Use self as phantom replacement and unlink.
// 如果 p 的颜色是黑色,则执行自平衡
if (p.color == BLACK)
fixAfterDeletion(p);
// 删除 p 和其父节点的相互指向
if (p.parent != null) {
// 如果 p 是父节点的左子节点,则置空父节点的左子节点
if (p == p.parent.left)
p.parent.left = null;
// 如果 p 是父节点的右子节点,则置空父节点的右子节点
else if (p == p.parent.right)
p.parent.right = null;
// 置空 p 对父节点的指向
p.parent = null;
}
}
}
- 删除的逻辑也就是刚才上面分析的一样。首先如果左右孩子都不为空,则调用一下的函数得到右子树中的最小值。然后修改被删除的结点的键值 表示替换。然后将要删除的结点指针指向找到的这个最小值。
- 相当于将最复杂的一种情况 转换成了上面的三种情况中的一种。接着就是对着三种情况的不同处理。
- 被删除节点有至少一个孩子。那么就将这个孩子替换被删除的结点。如果这个节点的颜色是黑色,则需要执行自平衡操作。
- 被删除的结点没有父亲结点 则将根结点设置为空
- 被删除节点没有孩子 则判断这个节点的颜色是不是黑色 若是黑色则执行自平衡。 然后删除这个节点。
static <K,V> TreeMap.Entry<K,V> successor(Entry<K,V> t) {
// <1> 如果 t 为空,则返回 null
if (t == null)
return null;
// <2> 如果 t 的右子树非空,则取右子树的最小值
else if (t.right != null) {
// 先取右子树的根节点
Entry<K,V> p = t.right;
// 再取该根节点的做子树的最小值,即不断遍历左节点
while (p.left != null)
p = p.left;
// 返回
return p;
// <3> 如果 t 的右子树为空
} else {
// 先获得 t 的父节点
Entry<K,V> p = t.parent;
// 不断向上遍历父节点,直到子节点 ch 不是父节点 p 的右子节点
Entry<K,V> ch = t;
while (p != null // 还有父节点
&& ch == p.right) { // 继续遍历的条件,必须是子节点 ch 是父节点 p 的右子节点
ch = p;
p = p.parent;
}
return p;
}
}
- 返回中序遍历中比t大的下一个值。
8. 自平衡
8.1插入后自平衡
private void fixAfterInsertion(Entry<K,V> x) {
x.color = RED;
// 以三层为一个单位
while (x != null && x != root && x.parent.color == RED) {// x 不为空 并且 x不是根结点 并且 x的父亲是红色的,
if (parentOf(x) == leftOf(parentOf(parentOf(x)))) {// 如果x的父亲是x父亲的父亲的左孩子
Entry<K,V> y = rightOf(parentOf(parentOf(x)));// y指向x的父亲的兄弟 也就是x父亲的父亲的右孩子
if (colorOf(y) == RED) {// 如果y是红色的 说明插入的结点不会导致二叉树失去平衡
setColor(parentOf(x), BLACK);// 设置x的父亲为黑色
setColor(y, BLACK);// 设置y的颜色为黑色
setColor(parentOf(parentOf(x)), RED);//设置x的父亲的父亲为红色 也就是y的父亲为红色
x = parentOf(parentOf(x));// x指向x的父亲的父亲
} else {//y是黑色 这就需要自平衡处理
if (x == rightOf(parentOf(x))) {// 如果x是父亲的右孩子
x = parentOf(x);// x指向x的父亲
rotateLeft(x);//左旋
}
setColor(parentOf(x), BLACK);//设置x的父亲为黑色
setColor(parentOf(parentOf(x)), RED);// 设置x的父亲的父亲为红色
rotateRight(parentOf(parentOf(x)));//右旋
}
} else {// x的父亲是x父亲的父亲的右孩子
Entry<K,V> y = leftOf(parentOf(parentOf(x)));//y指向x的父亲的兄弟 也就是x父亲的父亲的左孩子
if (colorOf(y) == RED) {//如果y是红色 说明插入的结点不会导致二叉树失去平衡
setColor(parentOf(x), BLACK);//这是x的父亲是黑色
setColor(y, BLACK);//设置y的颜色为黑色
setColor(parentOf(parentOf(x)), RED);//设置x的父亲的父亲为红色 也就是y的父亲为红色
x = parentOf(parentOf(x));//x指向x的父亲的父亲
} else {// y 是黑色 这就需要自平衡处理
if (x == leftOf(parentOf(x))) {// 如果x是父亲的左孩子
x = parentOf(x);// x指向x的父亲
rotateRight(x);//右旋
}
setColor(parentOf(x), BLACK);//设置x的父亲为黑色
setColor(parentOf(parentOf(x)), RED);// 设置设置x的父亲的父亲为红色
rotateLeft(parentOf(parentOf(x)));//左旋
}
}
}
root.color = BLACK;
}
在插入方法执行完后 需要执行上述的自平衡方法。可以把自平衡操作当前结点和他的父亲和他的爷爷看成一个单位。
三者的关系可以有如下四种
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-J9Iq7pHs-1597754100773)(/Users/gongsenlin/Library/Application Support/typora-user-images/截屏2020-08-18 下午4.52.06.png)]
上述的代码也是根据这4种情况进行处理的。最下面的是当前结点x。上面两个分别是x的父亲和x的父亲的父亲。
照着这个流程就可以懂得如何旋转来实现自平衡的了。
可以发现。当x的父亲的兄弟是红色的时候是不会导致二叉树失去平衡的 只有当x的父亲的兄弟是黑色的时候才会导致二叉树失去平衡。
不管是前两种情况还是后两种情况。执行的修改颜色的操作都是一样的。
8.2 删除后自平衡
有两种情况
一种是删除节点有孩子 则是在删除了节点后 以替换节点作为参数做自平衡
另一种是删除节点没有孩子 则说明是叶子节点,则直接根据这个节点作为参数做自平衡 然后删除这个节点。
具体的删除节点自平衡代码如下
private void fixAfterDeletion(Entry<K,V> x) {
while (x != root && colorOf(x) == BLACK) {// x不为根 且x的颜色是黑色
if (x == leftOf(parentOf(x))) {// 如果x是父亲的左孩子。
Entry<K,V> sib = rightOf(parentOf(x));//sib是x的兄弟
if (colorOf(sib) == RED) {//如果兄弟是红色 同时也说明x有兄弟 sib为空的情况下 颜色是默认黑色的
setColor(sib, BLACK);// 设置兄弟为黑色
setColor(parentOf(x), RED);//设置父亲为红色
rotateLeft(parentOf(x));// 左旋 以x的父亲为中心左旋
sib = rightOf(parentOf(x)); // sib指向
}
if (colorOf(leftOf(sib)) == BLACK &&
colorOf(rightOf(sib)) == BLACK) {//sib的 左右孩子都是黑色 则把它设置为红色 这是红黑树的一条规则
setColor(sib, RED);
x = parentOf(x);//x指向x的父亲
} else {
if (colorOf(rightOf(sib)) == BLACK) {//如果兄弟的右孩子是黑色
setColor(leftOf(sib), BLACK);//这是兄弟的做孩子为黑色
setColor(sib, RED);//设置sib的颜色为红色
rotateRight(sib);//右旋
sib = rightOf(parentOf(x)); // sib指向x的父亲的右孩子 也就是旋转后的x的兄弟
}
setColor(sib, colorOf(parentOf(x))); // 这是sib的颜色为x的父亲的颜色
setColor(parentOf(x), BLACK);//设置x的父亲的颜色为黑色
setColor(rightOf(sib), BLACK);// 设置sib的右孩子为黑色
rotateLeft(parentOf(x));// 左旋
x = root;//x指向根
}
} else { // x是父亲的右孩子
Entry<K,V> sib = leftOf(parentOf(x));//那么x的兄弟 就是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);
}
9. 查找接近的元素
在 NavigableMap 中,定义了四个查找接近的元素:
#lowerEntry(K key)
方法,小于key
的节点#floorEntry(K key)
方法,小于等于key
的节点#higherEntry(K key)
方法,大于key
的节点#ceilingEntry(K key)
方法,大于等于key
的节点
#ceilingEntry(K key)
方法,大于等于 key
的节点。代码如下:
public Map.Entry<K,V> ceilingEntry(K key) {
return exportEntry(getCeilingEntry(key));
}
static <K,V> Map.Entry<K,V> exportEntry(TreeMap.Entry<K,V> e) {
return (e == null) ? null :
new AbstractMap.SimpleImmutableEntry<>(e);
}
final Entry<K,V> getCeilingEntry(K key) {
Entry<K,V> p = root;
// 二叉查找遍历红黑树
while (p != null) {
// 比较 key
int cmp = compare(key, p.key);
// 当前节点比 key 大,则遍历左子树,这样缩小节点的值
if (cmp < 0) {
// 如果有左子树,则遍历左子树
if (p.left != null)
p = p.left;
// 如果没有,则直接返回该节点
else
return p;
// 当前节点比 key 小,则遍历右子树,这样放大节点的值
} else if (cmp > 0) {
// 如果有右子树,则遍历右子树
if (p.right != null) {
p = p.right;
} else {
// 找到当前的后继节点
Entry<K,V> parent = p.parent;
Entry<K,V> ch = p;
while (parent != null && ch == parent.right) {
ch = parent;
parent = parent.parent;
}
return parent;
}
// 如果相等,则返回该节点即可
} else
return p;
}
return null;
}
- 逻辑很简单。就是二叉查找。
- 如果遍历的当前结点大。则去左子树中找。如果左子树不存在 则返回当前结点。
- 如果遍历的当前结点小。则去右子树中找。如果右子树不存在。则去找当前结点的中序遍历的后继。然后返回。
#higherEntry(K key)
方法,大于 key
的节点。代码如下:
public Map.Entry<K,V> higherEntry(K key) {
return exportEntry(getHigherEntry(key));
}
final Entry<K,V> getHigherEntry(K key) {
Entry<K,V> p = root;
// 循环二叉查找遍历红黑树
while (p != null) {
// 比较 key
int cmp = compare(key, p.key);
// 当前节点比 key 大,则遍历左子树,这样缩小节点的值
if (cmp < 0) {
// 如果有左子树,则遍历左子树
if (p.left != null)
p = p.left;
// 如果没有,则直接返回该节点
else
return p;
// 当前节点比 key 小,则遍历右子树,这样放大节点的值
} else {
// 如果有右子树,则遍历右子树
if (p.right != null) {
p = p.right;
} else {
// 找到当前的后继节点
Entry<K,V> parent = p.parent;
Entry<K,V> ch = p;
while (parent != null && ch == parent.right) {
ch = parent;
parent = parent.parent;
}
return parent;
}
}
// 此处,相等的情况下,不返回
}
// 查找不到,返回 null
return null;
}
- 和
#ceilingEntry(K key)
逻辑的差异,相等的情况下,不返回该节点。
#ceilingEntry(K key)
方法,小于等于 key
的节点。代码如下:
public Map.Entry<K,V> floorEntry(K key) {
return exportEntry(getFloorEntry(key));
}
final Entry<K,V> getFloorEntry(K key) {
Entry<K,V> p = root;
// 循环二叉查找遍历红黑树
while (p != null) {
// 比较 key
int cmp = compare(key, p.key);
if (cmp > 0) {
// 如果有右子树,则遍历右子树
if (p.right != null)
p = p.right;
// 如果没有,则直接返回该节点
else
return p;
// 当前节点比 key 小,则遍历右子树,这样放大节点的值
} else if (cmp < 0) {
// 如果有左子树,则遍历左子树
if (p.left != null) {
p = p.left;
} else {
// 找到当前节点的前继节点
Entry<K,V> parent = p.parent;
Entry<K,V> ch = p;
while (parent != null && ch == parent.left) {
ch = parent;
parent = parent.parent;
}
return parent;
}
// 如果相等,则返回该节点即可
} else
return p;
}
// 查找不到,返回 null
return null;
}
- 思路没啥变化。
#getLowerEntry(K key)
方法,小于 key
的节点。代码如下:
public Map.Entry<K,V> lowerEntry(K key) {
return exportEntry(getLowerEntry(key));
}
final Entry<K,V> getLowerEntry(K key) {
Entry<K,V> p = root;
// 循环二叉查找遍历红黑树
while (p != null) {
// 比较 key
int cmp = compare(key, p.key);
// 当前节点比 key 小,则遍历右子树,这样放大节点的值
if (cmp > 0) {
// 如果有右子树,则遍历右子树
if (p.right != null)
p = p.right;
// 如果没有,则直接返回该节点
else
return p;
// 当前节点比 key 大,则遍历左子树,这样缩小节点的值
} else {
// 如果有左子树,则遍历左子树
if (p.left != null) {
p = p.left;
} else {
// 找到当前节点的前继节点
Entry<K,V> parent = p.parent;
Entry<K,V> ch = p;
while (parent != null && ch == parent.left) {
ch = parent;
parent = parent.parent;
}
return parent;
}
}
// 此处,相等的情况下,不返回
}
// 查找不到,返回 null
return null;
}
在一些场景下,我们并不需要返回 Entry 节点,只需要返回符合条件的 key 即可。所以有了对应的如下四个方法:
// TreeMap.java
public K lowerKey(K key) {
return keyOrNull(getLowerEntry(key));
}
public K floorKey(K key) {
return keyOrNull(getFloorEntry(key));
}
public K ceilingKey(K key) {
return keyOrNull(getCeilingEntry(key));
}
public K higherKey(K key) {
return keyOrNull(getHigherEntry(key));
}
static <K,V> K keyOrNull(TreeMap.Entry<K,V> e) {
return (e == null) ? null : e.key;
}
10. 获得首尾的元素
#firstEntry()
方法,获得首个 Entry 节点。代码如下:
public Map.Entry<K,V> firstEntry() {
return exportEntry(getFirstEntry());
}
final Entry<K,V> getFirstEntry() {
Entry<K,V> p = root;
if (p != null)
// 循环,不断遍历到左子节点,直到没有左子节点
while (p.left != null)
p = p.left;
return p;
}
-
通过不断遍历到左子节点,直到没有左子节点。
-
在
#getFirstEntry()
方法的基础上,还提供了另外两个方法:// TreeMap.java public Map.Entry<K,V> pollFirstEntry() { // 获得并移除首个 Entry 节点 // 获得首个 Entry 节点 Entry<K,V> p = getFirstEntry(); Map.Entry<K,V> result = exportEntry(p); // 如果存在,则进行删除。 if (p != null) deleteEntry(p); return result; } public K firstKey() { return key(getFirstEntry()); } static <K> K key(Entry<K,?> e) { if (e == null) // 如果不存在 e 元素,则抛出 NoSuchElementException 异常 throw new NoSuchElementException(); return e.key; }
#lastEntry()
方法,获得尾部 Entry 节点。代码如下:
// TreeMap.java
public Map.Entry<K,V> lastEntry() {
return exportEntry(getLastEntry());
}
final Entry<K,V> getLastEntry() {
Entry<K,V> p = root;
if (p != null)
// 循环,不断遍历到右子节点,直到没有右子节点
while (p.right != null)
p = p.right;
return p;
}
-
通过不断遍历到右子节点,直到没有右子节点。
-
在
#getLastEntry()
方法的基础上,还提供了另外两个方法:// TreeMap.java public Map.Entry<K,V> pollLastEntry() { // 获得并移除尾部 Entry 节点 // 获得尾部 Entry 节点 Entry<K,V> p = getLastEntry(); Map.Entry<K,V> result = exportEntry(p); // 如果存在,则进行删除。 if (p != null) deleteEntry(p); return result; } public K lastKey() { return key(getLastEntry()); }
在这里,补充一个 #containsValue(Object value)
方法,通过中序遍历的方式,遍历查找值为 value
的节点是否存在。代码如下:
// TreeMap.java
public boolean containsValue(Object value) {
for (Entry<K,V> e = getFirstEntry(); // 获得首个 Entry 节点
e != null; // 遍历到没有下一个节点
e = successor(e)) { // 通过中序遍历,获得下一个节点
if (valEquals(value, e.value)) // 判断值是否相等
return true;
}
return false;
}
static final boolean valEquals(Object o1, Object o2) {
return (o1==null ? o2==null : o1.equals(o2));
}
11. 清空
#clear()
方法,清空。代码如下:
// TreeMap.java
public void clear() {
// 增加修改次数
modCount++;
// key-value 数量置为 0
size = 0;
// 设置根节点为 null
root = null;
}
12. 克隆
#clone()
方法,克隆 TreeMap 。代码如下:
// TreeMap.java
public Object clone() {
// 克隆创建 TreeMap 对象
TreeMap<?,?> clone;
try {
clone = (TreeMap<?,?>) super.clone();
} catch (CloneNotSupportedException e) {
throw new InternalError(e);
}
// Put clone into "virgin" state (except for comparator)
// 重置 clone 对象的属性
clone.root = null;
clone.size = 0;
clone.modCount = 0;
clone.entrySet = null;
clone.navigableKeySet = null;
clone.descendingMap = null;
// Initialize clone with our mappings
// 使用自己,构造 clone 对象的红黑树
try {
clone.buildFromSorted(size, entrySet().iterator(), null, null);
} catch (java.io.IOException | ClassNotFoundException cannotHappen) {
}
return clone;
}
13. 序列化
#writeObject(ObjectOutputStream s)
方法,序列化 TreeMap 对象。代码如下:
// TreeMap.java
@java.io.Serial
private void writeObject(java.io.ObjectOutputStream s)
throws java.io.IOException {
// Write out the Comparator and any hidden stuff
// 写入非静态属性、非 transient 属性
s.defaultWriteObject();
// Write out size (number of Mappings)
// 写入 key-value 键值对数量
s.writeInt(size);
// Write out keys and values (alternating)
// 写入具体的 key-value 键值对
for (Map.Entry<K, V> e : entrySet()) {
s.writeObject(e.getKey());
s.writeObject(e.getValue());
}
}
14. 反序列化
#readObject(ObjectInputStream s)
方法,反序列化成 TreeMap 对象。代码如下:
// TreeMap.java
@java.io.Serial
private void readObject(final java.io.ObjectInputStream s)
throws java.io.IOException, ClassNotFoundException {
// Read in the Comparator and any hidden stuff
// 读取非静态属性、非 transient 属性
s.defaultReadObject();
// Read in size
// 读取 key-value 键值对数量 size
int size = s.readInt();
// 使用输入流,构建红黑树。
// 因为序列化时,已经是顺序的,所以输入流也是顺序的
buildFromSorted(size, null, s, null); // 注意,此时传入的是 s 参数,输入流
}
15. 转换成 Set/Collection
15.1 keySet
#keySet()
方法,获得正序的 key Set 。代码如下:
/**
* 正序的 KeySet 缓存对象
*/
private transient KeySet<K> navigableKeySet;
public Set<K> keySet() {
return navigableKeySet();
}
public NavigableSet<K> navigableKeySet() {
KeySet<K> nks = navigableKeySet;
return (nks != null) ? nks : (navigableKeySet = new KeySet<>(this));
}
15.2 descendingKeySet
#descendingKeySet()
方法,获得倒序的 key Set 。代码如下:
/**
* 倒序的 NavigableMap 缓存对象
*/
private transient NavigableMap<K,V> descendingMap;
public NavigableSet<K> descendingKeySet() {
return descendingMap().navigableKeySet();
}
public NavigableMap<K, V> descendingMap() {
NavigableMap<K, V> km = descendingMap;
return (km != null) ? km :
(descendingMap = new DescendingSubMap<>(this,
true, null, true,
true, null, true));
}
15.3 values
#values()
方法,获得 value 集合。代码如下:
public Collection<V> values() {
Collection<V> vs = values;
if (vs == null) {
vs = new Values();
values = vs; // values 缓存,来自 AbstractMap 的属性
}
return vs;
}
15.4 entrySet
#entrySet()
方法,获得 Entry 集合。代码如下:
/**
* Entry 缓存集合
*/
private transient EntrySet entrySet;
public Set<Map.Entry<K,V>> entrySet() {
EntrySet es = entrySet;
return (es != null) ? es : (entrySet = new EntrySet());
}