TreeMap 集合源码分析
建议
- TreeMap是一个可用来排序的Map集合。 底层是使用红黑树来存储数据的,节点为 Entry<K,V>,K 用来排序,V 为实际要存储的值。你可以传入自定义比较器来对K进行排序,也可使用TreeMap的自然排序。默认 左子节点更小,右子节点更大。
- 在学习TreeMap 源码之前一定要对红黑树有所了解才可以,不然很难看懂里面关于红黑树的一些操作。如果你的数据结构不太好,一下学习红黑树又觉得太困难,建议你先学 二叉树 -> 二叉搜索树 -> 平衡二叉搜索树(AVL树)-> B树 -> 红黑树,知识都是层层递进的。
一、字段分析
//用于比较K的比较器,从而对 K 进行排序
private final Comparator<? super K> comparator;
//红黑树的跟节点,默认为null
private transient Entry<K,V> root = null;
//实际存储数据的节点个数
private transient int size = 0;
//版本号(也有称呼修改计数器),每次操作集合是更新该值,用于使用迭代器迭代过程中对集合是否发生修改做判断,
//因为不允许迭代过程中对集合进行操作,否在抛出 并发修改异常 ConcurrentModificationException。
private transient int modCount = 0;
//用来操作 节点entry 的集合,也可用来获取 entry 的迭代器
private transient EntrySet entrySet;
//用来操作 节点key 的集合,功能非常的丰富,也可用来获取 key的迭代器。
private transient KeySet<K> navigableKeySet;
//NavigableMap 是 Java 集合框架中的一个接口,它扩展了 SortedMap 接口,提供了更丰富的导航和搜索功能。说人话就是提供了很多
//花里胡哨的根据key获取entry的方法,功能非常的丰富。从未用过...
private transient NavigableMap<K,V> descendingMap;
//红黑树节点颜色 红色
private static final boolean RED = false;
//红黑树节点颜色 黑色
private static final boolean BLACK = true;
//---------------------------------
//继承自父类AbstractMap,用来操作 节点 value 集合,和获取value的迭代器
transient Collection<V> values;
二、构造方法分析
//无参构造器
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);
}
//将 map 中的所有元素添加到 TreeMap中
public void putAll(Map<? extends K, ? extends V> map) {
//需要添加的元素数量
int mapSize = map.size();
//如果 当前 treeMap 没有元素 且 要添加的元素数量 > 0 且 参数map是有序的map
//1:为什么要 treeMap 为空呢?
//这个和 buildFromSorted 的构建有关系,buildFromSorted 必须从 treeMap 是空的状态构建才能是平衡的,具体可看下面
//buildFromSorted 的详细介绍。
if (size==0 && mapSize!=0 && map instanceof SortedMap) {
Comparator<?> c = ((SortedMap<?,?>)map).comparator();
if (c == comparator || (c != null && c.equals(comparator))) {
++modCount;
try {
//从有序的容器中构建map中 红黑树
buildFromSorted(mapSize, map.entrySet().iterator(),
null, null);
} catch (java.io.IOException cannotHappen) {
} catch (ClassNotFoundException cannotHappen) {
}
return;
}
}
//否则会循环遍历 map 一个一个节点进行添加到 treeMap 中
super.putAll(map);
}
//父类 AbstractMap 的方法
public void putAll(Map<? extends K, ? extends V> m) {
//将 m 的元素遍历添加到 TreeMap 中
for (Map.Entry<? extends K, ? extends V> e : m.entrySet())
//调用 在调用子类treeMap 方法添加元素,模版模式用的确实666
put(e.getKey(), e.getValue());
}
//通过传入有序的map 进行构建
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) {
}
}
//buildFromSorted 重载方法,该方法为构建 treemap 的入口。
private void buildFromSorted(int size, Iterator<?> it,
java.io.ObjectInputStream str,
V defaultVal)
throws java.io.IOException, ClassNotFoundException {
this.size = size;
root = buildFromSorted(0, 0, size-1, computeRedLevel(size),
it, str, defaultVal);
}
// se 个节点能够见多少层的红黑树
private static int computeRedLevel(int sz) {
int level = 0;
for (int m = sz - 1; m >= 0; m = m / 2 - 1)
level++;
return level;
}
//buildFromSorted 的重载方法,真正开始构建红黑树
//先介绍下参数的意义
//level:整个红黑树的构建是一层一层的构建的,level 表示需要构建多少层
//lo 和 hi: 表示可用的数据范围,初始开始时肯定是传入的map中所有的数据都可用,所以一开始是 [0,size - 1],lo为0,hi为size - 1
//it: 用来可获取可用元素的迭代器,是entrySet的迭代器,获取到的是 entry节点,包含 k 和 v
//str:同样是用来获取可用元素的,但是优先从 it中获取,it不能获取了才能从 str 中获取,和 it 作用一样,只是str是流
//default:默认值,只要是我们从 it 获 str 中获取的元素,都用 default 为 value 构建节点
//参数介绍完了,在从整个方法的功能上说明下他是具体怎么做的:
//整个方法的思想和归并排序的思想是一样的(先排序左边一部分(构建左子树),在排序右边一部分(构建右子树),在合并(左子树和右子树和父节点合并)),归并排序你可能不了解,其实和二叉树的后序遍历思想也是一样的,先遍历左子树,再遍历右子树,在到父节点。这个方法也是采用递归,想要构建红黑树,先将左子树构建成红黑树,再讲右子树构建成红黑树,左右子树
//和父节点构建一颗整树,同时左右字树的构建会递归,同样为先构建 子树的左右子树为一个红黑树,在合并。将一个问题拆分成了相同的子问题。
//那么如何保证平衡呢?很直接的将最后一层的节点都染红色,其他的全部为黑色。。。所以传入了 level(当前层数), redLevel(最的整数),
//使用 if(level == redLevel)将最后一层的节点全部染红,从而达到红黑树的平衡。
//了解了思想在看代码就可非常简单明了了
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;
Entry<K,V> left = null;
if (lo < mid)
//先构建左子树
left = buildFromSorted(level+1, lo, mid - 1, redLevel,
it, str, defaultVal);
// extract key and/or value from iterator or stream
K key;
V value;
if (it != null) {
if (defaultVal==null) {
Map.Entry<?,?> entry = (Map.Entry<?,?>)it.next();
key = (K)entry.getKey();
value = (V)entry.getValue();
} else {
key = (K)it.next();
value = defaultVal;
}
} else { // use stream
key = (K) str.readObject();
value = (defaultVal != null ? defaultVal : (V) str.readObject());
}
Entry<K,V> middle = new Entry<>(key, value, null);
// color nodes in non-full bottommost level red
//将最后一层染红
if (level == redLevel)
middle.color = RED;
if (left != null) {
middle.left = left;
left.parent = middle;
}
if (mid < hi) {
//在构建右子树
Entry<K,V> right = buildFromSorted(level+1, mid+1, hi, redLevel,
//最后左右字数和父节点合并
middle.right = right;
right.parent = middle;
}
return middle;
}
三、内部类分析
- Entry<K,V> :红黑树的节点类。
- K:进行排序。
- V:实际要存储的值。
static final class Entry<K,V> implements Map.Entry<K,V> {
//用来排序
K key;
//用来存储值
V value;
//左子节点
Entry<K,V> left = null;
//右子节点
Entry<K,V> right = null;
//父节点
Entry<K,V> parent;
//颜色,默认为黑色。但是我们插入时会设为红色,即每次put往 TreeMap 中添加新节点,新节点都是红色的。
//红色可以快速达到平衡。
boolean color = BLACK;
/**
* Make a new cell with given key, value, and parent, and with
* {@code null} child links, and BLACK color.
*/
Entry(K key, V value, Entry<K,V> parent) {
this.key = key;
this.value = value;
this.parent = parent;
}
/**
* Returns the key.
*
* @return the key
*/
public K getKey() {
return key;
}
/**
* Returns the value associated with the key.
*
* @return the value associated with the key
*/
public V getValue() {
return value;
}
/**
* Replaces the value currently associated with the key with the given
* value.
*
* @return the value associated with the key before this method was
* called
*/
public V setValue(V value) {
V oldValue = this.value;
this.value = value;
return oldValue;
}
public boolean equals(Object o) {
if (!(o instanceof Map.Entry))
return false;
Map.Entry<?,?> e = (Map.Entry<?,?>)o;
//可以看出红黑树节点的比较是key和value都相等才认为相等
return valEquals(key,e.getKey()) && valEquals(value,e.getValue());
}
public int hashCode() {
int keyHash = (key==null ? 0 : key.hashCode());
int valueHash = (value==null ? 0 : value.hashCode());
return keyHash ^ valueHash;
}
public String toString() {
return key + "=" + value;
}
}
- PrivateEntryIterator:抽象的Entry迭代器基类。
- 使用了
设计模式 - 模版模式
。使用了final 定义了获取元素的方法,防止子类重写。子类可直接使用,大大简化了代码量。
- 使用了
abstract class PrivateEntryIterator<T> implements Iterator<T> {
//迭代过程中,下一个可以获取到的 Entry节点
Entry<K,V> next;
//上一次访问到的 Entry 节点,用于remove 进行删除,每次remove都是上次nextEntry迭代获取到的值,
//所以不可连续调用remove
Entry<K,V> lastReturned;
//期待的版本号,用来校验迭代过程中是否发生过集合被修改的情况,被修改了会抛出并发修改异常。
int expectedModCount;
//传入的红黑树中序遍历的第一个节点
PrivateEntryIterator(Entry<K,V> first) {
//更新版本号为集合的版本号
expectedModCount = modCount;
//因为还没访问过节点,所以是null
lastReturned = null;
//第一次访问当然是第一个节点了
next = first;
}
//判断下一次迭代能否获取到元素
public final boolean hasNext() {
return next != null;
}
//迭代获取元素
final Entry<K,V> nextEntry() {
//记录本次节点获取到的元素,用于返回
Entry<K,V> e = next;
//如果迭代过程中发生集合被修改,该节点被删除了,则抛出异常
if (e == null)
throw new NoSuchElementException();
//检查迭代过程中集合是否被修改
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
//获取到e的后继节点,即中序遍历的下一个节点
next = successor(e);
//记录这次访问到的节点
lastReturned = e;
return e;
}
//迭代获取上一个节点、即前驱节点,也是中序遍历的上一个节点
final Entry<K,V> prevEntry() {
Entry<K,V> e = next;
if (e == null)
throw new NoSuchElementException();
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
//获取前驱节点
next = predecessor(e);
lastReturned = e;
return e;
}
//删除迭代得到的值
public void remove() {
//如果上一次没有迭代,则无法删除,删除的前提是必须迭代过
if (lastReturned == null)
throw new IllegalStateException();
//并发异常检查
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
// deleted entries are replaced by their successors
//--------------------------------
//这要连起来看,为什么要判断被删除节点是否有左右字树呢,这跟红黑树的性质有关,在红黑树中,如果被删除节点的度为2,
//则使用后继节点覆盖被删除节点,并且删除掉用来覆盖的后继节点,后继节点度为0或1。
if (lastReturned.left != null && lastReturned.right != null)
//这里虽然将next 设为了被删除节点,但是下次迭代也不会访问到这个被删除的节点,正如我说的度为2,
//删除过程中会更新为后继节点
next = lastReturned;
deleteEntry(lastReturned);
//---------------------------------
//更新版本号
expectedModCount = modCount;
//将上次访问元素置空,也说明连续调用remove会报错。
lastReturned = null;
}
}
- 再来看看基于模版PrivateEntryIterator的三个迭代器:
EntryIterator
:获取 entry 节点的迭代器。KeyIterator
:获取 entry 里的 key 的迭代器。ValueIterator
:获取 entry 里的 value 的迭代器。
final class EntryIterator extends PrivateEntryIterator<Map.Entry<K,V>> {
//这里传入的是中序遍历得到的第一个节点
EntryIterator(Entry<K,V> first) {
super(first);
}
public Map.Entry<K,V> next() {
//返回模版中nextEntry()获取到的节点
return nextEntry();
}
}
final class KeyIterator extends PrivateEntryIterator<K> {
//这里传入的是中序遍历得到的第一个节点
KeyIterator(Entry<K,V> first) {
super(first);
}
public K next() {
//返回模版中nextEntry()获取到的节点的key
return nextEntry().key;
}
}
final class ValueIterator extends PrivateEntryIterator<V> {
//这里传入的是中序遍历得到的第一个节点
ValueIterator(Entry<K,V> first) {
super(first);
}
public V next() {
//返回模版中nextEntry()获取到的节点
return nextEntry().value;
}
}
- 那么三个迭代器该如何获取到呢。TreeMap 也分别提供了内部类来获取相应的迭代器。
- EntrySet:用来操作 entry 的集合,可获取EntryIterator迭代器 对 entry 进行迭代。
- KeySet:用来操作 key 的集合,可获取 KeyIterator 迭代器对 key 进行迭代。
- Values :用来操作 value 的集合,可获取 ValueIterator 对 value 进行迭代。
class EntrySet extends AbstractSet<Map.Entry<K,V>> {
public Iterator<Map.Entry<K,V>> iterator() {
return new EntryIterator(getFirstEntry());
}
...
}
static final class KeySet<E> extends AbstractSet<E> implements NavigableSet<E> {
public Iterator<E> iterator() {
if (m instanceof TreeMap)
return ((TreeMap<E,?>)m).keyIterator();
else
return ((TreeMap.NavigableSubMap<E,?>)m).keyIterator();
}
...
}
class Values extends AbstractCollection<V> {
public Iterator<V> iterator() {
return new ValueIterator(getFirstEntry());
}
...
}
四、 方法分析
- 添加元素方法。其实就是操作红黑树,在红黑树添加元素然后恢复平衡,如果了解红黑树的操作的话,看代码就会比较简单了。我前面也总结了红黑树的操作情况,可以参考下,我列举的情况使用了编号对应了下,方便对照。
//向TreeMap 中添加元素,如果 key 对应的entry 已存在,返回已存在 value 并使用传入的 value进行覆盖。如果没有key对应的
//entry,返回null,红黑树的节点插入一定是插入到叶子节点中去的。我说的是:插入到叶子节点中去!!!,红黑树的叶子节点都是虚构
//出来的空节点,我们插入位置就是这些空节点的位置,这个和的二叉树对叶子节点的定义不同,需要注意。
public V put(K key, V value) {
//首先获取到根节点
Entry<K,V> t = root;
//如果根节点为空,则是第一次添加元素,无需调整平衡
if (t == null) {
//这里是检查key,如果你传了比较器,并且比较器里是对null值做了处理的,ok没问题,但是如果传入的比较器不支持null,
//或者没传入比较器,那么是不允许key为 null 的,会报错
compare(key, key); // type (and possibly null) check
//新添加的节点为跟节点,新建节点默认为黑色
root = new Entry<>(key, value, null);
//节点数量 + 1
size = 1;
//版本 + 1
modCount++;
return null;
}
//记录比较结果
int cmp;
//记录父节点
Entry<K,V> parent;
// split comparator and comparable paths
Comparator<? super K> cpr = comparator;
//使用用户传入的比较器查找新节点应该插入哪个父节点下面
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 {
//用户没传入比较器,使用 Key class 自己的比较方法比较,同样查找新节点应该插入到哪个父节点下面
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);
//判断插入到父节点的哪边,插入一定是插入到叶子节点中去的,
if (cmp < 0)
parent.left = e;
else
parent.right = e;
//插入后调整红黑树的平衡,使之符合五条性质
fixAfterInsertion(e);
//节点数量 + 1
size++;
//版本 + 1
modCount++;
return null;
}
//插入元素后恢复红黑树的平衡
private void fixAfterInsertion(Entry<K,V> x) {
//插入新元素默认为红色,红色便可以满足红黑树的四条性质,可以更快速满足五条性质恢复平衡
x.color = RED;
//父节点为红色
while (x != null && x != root && x.parent.color == RED) {
//这个if和下面的else 只需要看一个即可,是完全对称的操作。
//parent = grand.left,即父节点是祖父节点的左节点
if (parentOf(x) == leftOf(parentOf(parentOf(x)))) {
//获取到 uncle 节点
Entry<K,V> y = rightOf(parentOf(parentOf(x)));
//情况3:如果uncle是红色节点,则进行上溢操作来恢复平衡
if (colorOf(y) == RED) {
//父节点染黑
setColor(parentOf(x), BLACK);
//uncle 节点染黑色
setColor(y, BLACK);
//grand 染成红色
setColor(parentOf(parentOf(x)), RED);
//循环迭代向上调整平衡
x = parentOf(parentOf(x));
} else {
//情况2:uncle 为黑色,则通过旋转来回复平衡
//父节点是grand的右节点,即 R
if (x == rightOf(parentOf(x))) {
x = parentOf(x);
//左旋转
rotateLeft(x);
}
//父节点染黑色
setColor(parentOf(x), BLACK);
//grand节点染红
setColor(parentOf(parentOf(x)), RED);
//右旋转
rotateRight(parentOf(parentOf(x)));
}
} else {
//对称操作 ,left变right,right变left
Entry<K,V> y = leftOf(parentOf(parentOf(x)));
//情况3:如果uncle是红色节点,则进行上溢操作来恢复平衡
if (colorOf(y) == RED) {
setColor(parentOf(x), BLACK);
setColor(y, BLACK);
setColor(parentOf(parentOf(x)), RED);
x = parentOf(parentOf(x));
} else {
//情况2:uncle 为黑色,则通过旋转来回复平衡
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;
}
- 删除操作。这里值所说的情况是和这篇文章里所总结的各种情况是一一对应的。
//删除 节点k = key 的节点。
public V remove(Object key) {
//获取到该key 的节点
Entry<K,V> p = getEntry(key);
//如果为存储为 key 的节点,则删除失败。
if (p == null)
return null;
//找到了 为key 的几点,获取该节点的value,用于返回告知用户哪个 value 被删除了
V oldValue = p.value;
//删除查找到的节点p
deleteEntry(p);
//返回查找到的 value
return oldValue;
}
//删除节点p
private void deleteEntry(Entry<K,V> p) {
//版本 + 1
modCount++;
//节点数量 - 1
size--;
// If strictly internal, copy successor's element to p and then make p
// point to successor.
// //情况1:如果当前节点为度为2,使用后继节点替换掉要被删除的节点p,在删除掉后继节点
if (p.left != null && p.right != null) {
//p 的后继节点p
Entry<K,V> s = successor(p);
//使用后继节点替换被删除的节点
p.key = s.key;
p.value = s.value;
//在删除后继节点,因为p的含义就是将要被删除的节点
p = s;
} // p has 2 children
// Start fixup at replacement node, if it exists.
//执行到这里说明 p 的节点度为1 或 o
//节点p的孩子节点,会被用来替换掉节点p,至于如何替换还需分情况讨论
Entry<K,V> replacement = (p.left != null ? p.left : p.right);
情况1:如果度为1
if (replacement != null) {
// Link replacement to parent
//获取父节点
replacement.parent = p.parent;
//如果被删除节点p是根节点,则孩子节点就是新的根节点了
if (p.parent == null)
root = replacement;
//被删除节点不是跟节点,且p是父节点的左子节点,则更新父节点的左子节点为 p 的孩子节点
else if (p == p.parent.left)
p.parent.left = replacement;
else
//同样的被删除节点不是跟节点,且p是父节点的右子节点,则更新父节点的右子节点为 p 的孩子节点
p.parent.right = replacement;
// Null out links so they are OK to use by fixAfterDeletion.
//断开节点p
p.left = p.right = p.parent = null;
// Fix replacement
//如果被删除节点p度为1 且 是红色节点,则无需调整红黑树的平衡,删除结束。
if (p.color == BLACK)
//这个对应情况2:node节点为黑色,且子节点为红色,则使用子节点代替node节点,并染黑即可,染黑在 fixAfterDeletion里会体现出来
fixAfterDeletion(replacement);
} else if (p.parent == null) { // return if we are the only node.
//只有节点 p 的情况
root = null;
} else { // No children. Use self as phantom replacement and unlink.
情况3和4:被删除节点为黑色,调整平衡
if (p.color == BLACK)
fixAfterDeletion(p);
//删除node节点,node可能调整了位置,比如3.3情况,会发生上溢,所以还需要判断是否为null
if (p.parent != null) {
if (p == p.parent.left)
p.parent.left = null;
else if (p == p.parent.right)
p.parent.right = null;
p.parent = null;
}
}
}
//节点被删除后,调整红黑树的平衡
private void fixAfterDeletion(Entry<K,V> x) {
//如果是前面的情况2,不会走循环,直接染黑即可。
while (x != root && colorOf(x) == BLACK) {
//如果当前节点x 是父节点的左节点
if (x == leftOf(parentOf(x))) {
//获取到节点 x 的兄弟接单
Entry<K,V> sib = rightOf(parentOf(x));
//情况4:被删除节点为黑色,且兄弟节点为红色
if (colorOf(sib) == RED) {
setColor(sib, BLACK);
setColor(parentOf(x), RED);
rotateLeft(parentOf(x));
//旋转之后,兄弟节点变化了,进行更新.之后套用 情况3.2
sib = rightOf(parentOf(x));
}
//到这里 sibling兄弟节点 必为黑色了
//情况3.2 和 情况 3.3:被删除节点为黑色,且兄弟节点为黑色,且兄弟节点没有一个红色子节点
if (colorOf(leftOf(sib)) == BLACK &&
colorOf(rightOf(sib)) == BLACK) {
//将兄弟节点染红,然后递归调用
setColor(sib, RED);
x = parentOf(x);
} else {
//情况3.1 : 被删除节点node为黑,兄弟节点为黑,且兄弟节点至少有一个红色子节点,则需要判断旋转方向进行旋转即可
if (colorOf(rightOf(sib)) == BLACK) {
setColor(leftOf(sib), BLACK);
setColor(sib, RED);
rotateRight(sib);
sib = rightOf(parentOf(x));
}
//旋转之后新的父节点要继承原先的父节点
setColor(sib, colorOf(parentOf(x)));
setColor(parentOf(x), BLACK);
setColor(rightOf(sib), BLACK);
rotateLeft(parentOf(x));
//赋值root,结束循环,因为root必为黑
x = root;
}
} else { // symmetric
//全是对称情况,left 变 right ,right 变 left
Entry<K,V> sib = leftOf(parentOf(x));
//情况4:被删除节点为黑色,且兄弟节点为红色
if (colorOf(sib) == RED) {
setColor(sib, BLACK);
setColor(parentOf(x), RED);
rotateRight(parentOf(x));
//旋转之后,兄弟节点变化了,进行更新.之后套用 情况3.2
sib = leftOf(parentOf(x));
}
//到这里 sibling兄弟节点 必为黑色了
//情况3.2 和 情况 3.3:被删除节点为黑色,且兄弟节点为黑色,且兄弟节点没有一个红色子节点
if (colorOf(rightOf(sib)) == BLACK &&
colorOf(leftOf(sib)) == BLACK) {
setColor(sib, RED);
x = parentOf(x);
} else {
//情况3.1 : 被删除节点node为黑,兄弟节点为黑,且兄弟节点至少有一个红色子节点,则需要判断旋转方向进行旋转即可
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));
//赋值root,结束循环,因为root必为黑
x = root;
}
}
}
setColor(x, BLACK);
}
-
TreeMap 最核心的就是添加元素和删除元素的方法,当然还有很多其他方法但也都是操作红黑树的技巧,遍不在过多介绍了。在说一个TreeMap 获取得带的方法。
-
values():这个方法可以取到 红黑树的所有键值对里的 values 值,测试如下:
-
values()方法源码如下:
//这是 TreeMap 继承自父类 AbstractMap 的字段 transient Collection<V> values; public Collection<V> values() { //TreeMap 第一次调用该方法之前,values 默认是空的 Collection<V> vs = values; if (vs == null) { //所以第一次会执行该代码 vs = new Values(); values = vs; } return vs; } class Values extends AbstractCollection<V> { public Iterator<V> iterator() { return new ValueIterator(getFirstEntry()); } public int size() { return TreeMap.this.size(); } public boolean contains(Object o) { return TreeMap.this.containsValue(o); } public boolean remove(Object o) { for (Entry<K,V> e = getFirstEntry(); e != null; e = successor(e)) { if (valEquals(e.getValue(), o)) { deleteEntry(e); return true; } } return false; } public void clear() { TreeMap.this.clear(); } public Spliterator<V> spliterator() { return new ValueSpliterator<K,V>(TreeMap.this, null, null, 0, -1, 0); } }
-
可能会有如下疑问:
- 为何 TreeMap 仅仅是调用了 values方法,就可以拿到所有的键值对的value 值?
- 看源码也仅仅是调用了 Values 无参构造函数进行了实例化,并没有调用迭代器方法,没有经理迭代的过程,那么如何将键值对的 value 全部拿给 集合 Value 并返回的呢?
-
我们可以观察下测试代码的字节码:
public class Test { public Test() { } public static void main(String[] args) { TreeMap<Integer, Integer> treeMap = new TreeMap(); for(int i = 0; i < 5; ++i) { treeMap.put(i, i); } Collection<Integer> values = treeMap.values(); Iterator var3 = values.iterator(); while(var3.hasNext()) { Integer value = (Integer)var3.next(); System.out.println(value); } System.out.println(""); } }
-
真相大白了,其实你去获取集合 values 的值的时候,首先会获取 Value 里的迭代器,然后调用该迭代器获取值,自始至终并没有将键值对的value取出来的给Value集合的过程,所以这种想法是错误的,我们在看下Value获取迭代的代码:就是获取到我们前面说的 Value的迭代器:ValueIterator,所以获取到的 value 其实是直接从键值对里面遍历拿到的。Value集合可以想象是外面套的一层壳,内部还是操作键值对。这个在很多其他源码中都有体现,比如HashMap中。
五、 总结
- TreeMap是一个有序的容器,底层是使用了红黑树存储来存储数据,来保证它的有序性,通过红黑树节点的k来进行排序。
- 默认是不支持存储key 为null的,因为 在使用 compare 比较之前会强行转换为 Comparable,所以key 为null 会报错,同时也说明,如果没有传入比较器,那么传入的key 的数据类型得是 Comparable 的实现类。
- 为什么采用红黑树,而不是采用像AVL这种同样是二叉平衡二叉搜索树呢?
- 首先确实单单从搜索速率上来说AVL更快,因为AVL数是一种强平衡树,即左右字数高度差不为超过1,更加平衡,所以层级也就越低,搜索时可以更少的判断是去左子树查还是右子树差。而红黑树是弱平衡的,高度差会超过1,是通过五条性质来维护平衡,所以层数更高,所以搜索时,左右字数判断次数可能会更多。
- TreeMap 不仅仅用来搜索,我们还会对其进行大量的添加与删除操作,而添加与删除的性能,红黑树大于AVL树,因为AVL恢复平衡需要旋转,而旋转后可能导致父类不平衡,继续向上调整直到平衡,包括删除也是同理,而红黑树则只需要通过一次或两次的旋转操作即可达到平衡,即便是出现上溢的情况,也只需通过染色即可恢复平衡,所需速度更快。
- 所以基于还会有大量添加与删除操作的场景,红黑树的性能由于AVAL树,所以实际应用中更多的选择红黑树。
- 同时提供了三种迭代器,分别是 节点 entry 的迭代器,key 的迭代器和value 的迭代器,可方便获取想要的信息,同时迭代器的设计包括 TreeMap 的设计都用到了 模版模式,比如 TreeMap 中定义了 抽象的 迭代器基类,并实现了通用的方法,在其他的迭代器中继承他即可使用这些方法,大大提高了代码利用率。