Map容器家族(TreeMap源码详解)
一、在Map集合家族的位置及概述
TreeMap是一个有序的key-value集合,它内部是通过红-黑树实现的。TreeMap继承与AbstractMap,实现了NavigableMap接口,意味着它支持一系列的导航方法,比如返回有序的key集合。它还实现了Cloneable接口,意味着它能被克隆。另外也实现了Serializable接口,表示它支持序列化。TreeMap是基于红-黑树实现的,该映射根据其key的自然顺序进行排序,或者根据用户创建映射时提供的Comarator进行排序,另外,TreeMap是非同步的。
不了红黑树,可以看我的这篇博客。
二、成员变量
private final Comparator<? super K> comparator; // 保证有序比较器,默认自然排序
private transient Entry<K,V> root; // TreeMap中存储元素的红黑树根节点
private transient int size = 0; // 元素个数
private transient int modCount = 0; // 修改次数
private transient EntrySet entrySet; // 存储所有键值对的集合
private transient KeySet<K> navigableKeySet;
private transient NavigableMap<K,V> descendingMap;
private static final Object UNBOUNDED = new Object();
private static final boolean RED = false; // 红黑树的节点颜色--红色
private static final boolean BLACK = true; // 红黑树的节点颜色--黑色
三、存储元素的红黑树节点
// 红黑树的节点
static final class Entry<K,V> implements Map.Entry<K,V> {
K key; // 键
V value; // 值
Entry<K,V> left; // 左孩子
Entry<K,V> right; // 右孩子
Entry<K,V> parent; // 父亲
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;
}
……
}
四、构造方法
// 无参构造,默认自然排序
public TreeMap() {
comparator = null;
}
// 带比较器的构造器
// 如果参数为null,则使用默认比较器
public TreeMap(Comparator<? super K> comparator) {
this.comparator = comparator;
}
// 使用参数map集合,构造该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) {
}
}
其中的涉及到的方法:putAll方法和buildFromSorted方法
1.putAll(Map<? extends K, ? extends V> map)
// 将参数map中所有的元素添加到本集合中
public void putAll(Map<? extends K, ? extends V> map) {
int mapSize = map.size(); // 参数map中元素个数
if (size==0 && mapSize!=0 && map instanceof SortedMap) { // 本集合为空且参数集合有元素
Comparator<?> c = ((SortedMap<?,?>)map).comparator(); // 获取参数的比较器
if (c == comparator || (c != null && c.equals(comparator))) { // 本集合的比较器和参数中的比较器相等
++modCount; // 修改次数自己1
try { // 构建
buildFromSorted(mapSize, map.entrySet().iterator(),
null, null);
} catch (java.io.IOException cannotHappen) {
} catch (ClassNotFoundException cannotHappen) {
}
return; // 方法结束
}
}
super.putAll(map); // 如果不满足上述条件使用父类的putAll方法添加元素
}
当本集合的元素个数为0、参数map中有元素且和本集合的构造器相同,则使用buildFromSorted方法初始化本集合的红黑树(TreeMap的核心)。当不满足上诉条件时调用父亲的putAll方法:如下
public void putAll(Map<? extends K, ? extends V> m) {
for (Map.Entry<? extends K, ? extends V> e : m.entrySet())
put(e.getKey(), e.getValue());
}
分类这个putAll方法内使用增强for遍历entrySet键值对集合,在使用put方法,将元素添加进本集合;由于子类TreeMap本身重写了put方法,则在此处调用的是子类的put方法:如下
// 实现父类的put方法
public V put(K key, V value) {
Entry<K,V> t = root; // 根节点
if (t == null) { //当红黑树为空
compare(key, key); // type (and possibly null) check // 使用比较方法检查键不为null
root = new Entry<>(key, value, null); // 创建节点,并作为根节点
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 { // 比较器为空,使用默认比较器
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;
// 红黑树修复
// 当有节点插入,则会破坏红黑树的5条性质,此时需要通过节点的颜色变换和左右旋转来使树满足性质
fixAfterInsertion(e);
size++;
modCount++;
return null;
}
到这里putAll方法还没有结束,还有buildFromSorted方法。
2.buildFromSorted方法
该方法有两个不同的重载方法,一个是构建根节点的,另一个是递归循环构建出左右子支的。
1)buildFromSorted(int size, Iterator<?> it, java.io.ObjectInputStream str, V defaultVal)方法
// 线性时间树构建算法来自排序数据
private void buildFromSorted(int size, Iterator<?> it,
java.io.ObjectInputStream str,
V defaultVal)
throws java.io.IOException, ClassNotFoundException {
this.size = size;
// 使用buildFromSorted构建出根节点
root = buildFromSorted(0, 0, size-1, computeRedLevel(size),
it, str, defaultVal);
}
2)buildFromSorted(int level, int lo, int hi, int redLevel, Iterator<?> it, java.io.ObjectInputStream str, V defaultVal)方法
通过该方法的递归调用,巧妙的实现了用参数map集合中元素构建本集合的红黑树。在递归调用的时候,引入了二分查找的思想,巧妙的将红黑树的递归构建与二分查找的思想相结合。首先创建根节点,之后递归调用到左子分支的叶子结点,反复构建。相比现在看源码的时候以红黑树的递归创建为着眼节点,以二分查找的思想巧妙的实现递归调用。代码如下:
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 {
/**
* 策略:树的根节点是参数map集合最中间的元素。为了得到它,我们必须
* 先递归构建根节点的整个左子分支,以便抓住它的所有元素。然后继续
* 去构建右子分支。
* 低位lo和高位hi参数是最小和最大值用于迭代遍历。他们不是
* 真正的索引,只是顺序进行,确保提取的顺序与之相对应。
*/
if (hi < lo) return null; // 此方法的递归结束条件,低位大于等于高位结束
int mid = (lo + hi) >>> 1; // 无符号右移一位,等同于除2。计算机的位移操作比%2操作效率高
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(); // 获取entry
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,
it, str, defaultVal);
middle.right = right;
right.parent = middle;
}
return middle;
}
五、常用API
1.添加元素
添加元素有两个方法,put和putAll方法,在上面的已经讲过,参见上面的讲解内容。
2.删除元素
public void clear() { // 一看见就懂
modCount++;
size = 0;
root = null;
}
// 根据键删除键值对
public V remove(Object key) {
// 通过键获取键值对
Entry<K,V> p = getEntry(key);
if (p == null) // 为空,则表示集合中没有该键值对,直接返回null
return null;
V oldValue = p.value; // 获取旧值,并直接返回
deleteEntry(p); // 删除操作
return oldValue;
}
其中涉及到的方法getEntry(Object key)、deleteEntry(Entry<K,V> p)
// 根据键获取键值对
final Entry<K,V> getEntry(Object key) {
// Offload comparator-based version for sake of performance
// 构造器不为空,使用getEntryUsingComparator方法
// 为空跳过,使用下面方法
if (comparator != null)
return getEntryUsingComparator(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);
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) {
@SuppressWarnings("unchecked")
K k = (K) key; // 强制类型转换
Comparator<? super K> cpr = comparator; // 构造器
if (cpr != null) { // 构造器不为空执行,否者返回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; // 找不到返回null
}
// 删除节点并且平衡红黑树
private void deleteEntry(Entry<K,V> p) {
modCount++; // 修改次数加1
size--; // 长度减1
// If strictly internal, copy successor's element to p and then make p
// point to successor.
// 如果严格内部,将后继元素复制到p,然后使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变量指向堆中的s变量所指向的对象, 即将p指向已经需要移除的后继节点
} // p has 2 children
// Start fixup at replacement node, if it exists.
// 开始修复被移除节点处的树结构
// 如果p有左孩子,取左孩子,否则取右孩子
// p现在指向的是待删除节点的后继节点(次大于待删除节点的),即它的右子分子的最左孩子,
// 所以,p一定没有左孩子, 可能有右孩子
Entry<K,V> replacement = (p.left != null ? p.left : p.right);
if (replacement != null) {
// Link replacement to parent
replacement.parent = p.parent;
// p节点没有父节点,即p节点是根节点
if (p.parent == null)
root = replacement;
// p是其父节点的左孩子
else if (p == p.parent.left)
// 将p的父节点的left引用指向replacement
// 这步操作实现了删除p的父节点到p节点的引用
p.parent.left = replacement;
else
// 如果p是其父节点的右孩子,将父节点的right引用指向replacement
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
// 如果后继节点是黑色,在删除移动完成后需要进行修复(黑节点需要修复,红节点不需要修复)
// 在删除节点后修复红黑树的颜色分配
if (p.color == BLACK)
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.
// 如果是黑色,调整树结构
if (p.color == BLACK)
fixAfterDeletion(p);
// 这个判断也一定会通过,因为p.parent如果不是null则在上面的else if块中已经被处理
if (p.parent != null) {
// p是一个左孩子
if (p == p.parent.left)
// 删除父节点对p的引用
p.parent.left = null;
// p是一个右孩子
else if (p == p.parent.right)
// 删除父节点对p的引用
p.parent.right = null;
// 删除p节点对父节点的引用
p.parent = null;
}
}
}
// 返回指定Entry的后继者,如果不是,则返回null。
// 此处获取的后继节点是t节点的次大节点
static <K,V> TreeMap.Entry<K,V> successor(Entry<K,V> t) {
if (t == null) // t为空直接返回null
return null;
else if (t.right != null) { // t的右孩子存在,遍历其右孩子到最左叶子节点,即为次大节点
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) {
// 循环处理,条件为x不是root节点且是黑色的
while (x != root && colorOf(x) == BLACK) {
// x是一个左孩子
if (x == leftOf(parentOf(x))) {
// 获取x的兄弟节点sib
Entry<K, V> sib = rightOf(parentOf(x));
// sib是红色的
if (colorOf(sib) == RED) {
// 将sib设置为黑色
setColor(sib, BLACK);
// 将父节点设置成红色
setColor(parentOf(x), RED);
// 左旋父节点
rotateLeft(parentOf(x));
// sib移动到旋转后x的父节点p的右孩子
sib = rightOf(parentOf(x));
}
// sib的两个孩子的颜色都是黑色(null返回黑色)
if (colorOf(leftOf(sib)) == BLACK &&
colorOf(rightOf(sib)) == BLACK) {
// 将sib设置成红色
setColor(sib, RED);
// x移动到x的父节点
x = parentOf(x);
} else { // sib的左右孩子都是黑色的不成立
// sib的右孩子是黑色的
if (colorOf(rightOf(sib)) == BLACK) {
// 将sib的左孩子设置成黑色
setColor(leftOf(sib), BLACK);
// sib节点设置成红色
setColor(sib, RED);
// 右旋操作
rotateRight(sib);
// sib移动到旋转后x父节点的右孩子
sib = rightOf(parentOf(x));
}
// sib设置成和x的父节点一样的颜色
setColor(sib, colorOf(parentOf(x)));
// x的父节点设置成黑色
setColor(parentOf(x), BLACK);
// sib的右孩子设置成黑色
setColor(rightOf(sib), BLACK);
// 左旋操作
rotateLeft(parentOf(x));
// 设置调整完的条件:x = root跳出循环
x = root;
}
} else { // symmetric// x是一个右孩子
// 获取x的兄弟节点
Entry<K, V> sib = leftOf(parentOf(x));
// 如果sib是红色的
if (colorOf(sib) == RED) {
// 将sib设置为黑色
setColor(sib, BLACK);
// 将x的父节点设置成红色
setColor(parentOf(x), RED);
// 右旋
rotateRight(parentOf(x));
// sib移动到旋转后x父节点的左孩子
sib = leftOf(parentOf(x));
}
// sib的两个孩子的颜色都是黑色(null返回黑色)
if (colorOf(rightOf(sib)) == BLACK &&
colorOf(leftOf(sib)) == BLACK) {
// sib设置为红色
setColor(sib, RED);
// x移动到x的父节点
x = parentOf(x);
} else {// sib的两个孩子的颜色都是黑色(null返回黑色)不成立
// sib的左孩子是黑色的,或者没有左孩子
if (colorOf(leftOf(sib)) == BLACK) {
// 将sib的右孩子设置成黑色
setColor(rightOf(sib), BLACK);
// sib节点设置成红色
setColor(sib, RED);
// 左旋
rotateLeft(sib);
// sib移动到x父节点的左孩子
sib = leftOf(parentOf(x));
}
// sib设置成和x的父节点一个颜色
setColor(sib, colorOf(parentOf(x)));
// x的父节点设置成黑色
setColor(parentOf(x), BLACK);
// sib的左孩子设置成黑色
setColor(leftOf(sib), BLACK);
// 右旋
rotateRight(parentOf(x));
// 设置跳出循环的标识
x = root;
}
}
}
// 将x设置为黑色
setColor(x, BLACK);
}
3.查询元素
// 根据键获取值
public V get(Object key) {
// 调用getEntry方法获取值
Entry<K, V> p = getEntry(key);
// 如果p为空返回null,否则返回值
return (p == null ? null : p.value);
}
4.遍历操作
// 返回包含所有键值对的Set视图集合
public Set<Map.Entry<K, V>> entrySet() {
EntrySet es = entrySet; // 获取成员变量entrySet值
// 如果es为空,会创建EntrySet,在初次创建时会被初始化,以后就不用再初始化了
return (es != null) ? es : (entrySet = new EntrySet());
}
EntrySet类为实现了AbstractSet的子类,其中的方法基本同父类。另外还有两种遍历方式:
1)使用Set<K> keySet()方法
2)使用Collection<V> values()方法
六、总结
TreeMap的底层数据结构是红黑树,其基本的增删改查操作都是对红黑树的操作,在其本身的增删改查的基础上又扩展了一些其他方法。