TreeMap
的排序可以通过键(实现 Comparable
接口),或者在创建 Map 时,提供的 Comparator
实现。
文档中提到有趣的一点,containsKey
,get
,put
,remove
操作提供了 log(n)
时间成本,所以带着这个问题去看。
初始化
TreeMap
的构造器有如下几个:
public TreeMap() {
comparator = null;
}
public TreeMap(Comparator<? super K> comparator) {
this.comparator = comparator;
}
public TreeMap(Map<? extends K, ? extends V> m) {
comparator = null;
putAll(m);
}
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) {
}
}
我们可以对比它与 HashMap
的构造函数,就知道它的底层数据结构已经变了。提供比较器,是因为 TreeMap
是支持自定义排序的,而不像 LinkedHashMap
仅仅只能按照插入顺序或者访问顺序进行排序。
查看 buildFromSorted
方法的实现:
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);
}
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 {
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,
it, str, defaultVal);
middle.right = right;
right.parent = middle;
}
return middle;
}
代码过程比较抽象,转换成图过程说明:
上面树结构的迭代顺序为 【1,2,3,4,5,6,7,8,9】,采用了中序遍历,这很重要,因为代码中构建树的数据来源就是迭代器,也就是我们只能按照这样的顺序重新构造树节点。
结合上面代码,分析图:
带有彩色的数字,代表执行顺序;节点真正的创建时机,结合代码分析可知,在递归返回时,开始创建节点。以上是先递归构造左子树,然后递归构造右子树。可以发现上面节点创建时机,将完全符合迭代器中的顺序。
注意,
TreeMap
可能不会构造成这样一模一样的树,在这里只是借此说明代码的执行过程。
这里思考一个问题,既然是递归,那么就有可能出现栈溢出,对此,做了测试,结果总是抛出 OutOfMemoryError
。
存取操作
put 操作将存储新的映射,如果当前 key 之前并未存在的话;如果存在,则覆盖,并返回旧值。
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;
// split comparator and comparable paths
Comparator<? super K> cpr = comparator;
// 1. 比较器存在的情况下
if (cpr != null) {
// 1.1. 循环查找父节点
do {
parent = t;
cmp = cpr.compare(key, t.key);
if (cmp < 0)
t = t.left;
else if (cmp > 0)
t = t.right;
// 1.1.1 找到相等的节点,直接替代,然后返回
else
return t.setValue(value);
} while (t != null);
}
// 2. 比较器不存在的情况下
else {
if (key == null)
throw new NullPointerException();
@SuppressWarnings("unchecked")
Comparable<? super K> k = (Comparable<? super K>) key;
// 2.1 循环查找父节点
do {
parent = t;
cmp = k.compareTo(t.key);
if (cmp < 0)
t = t.left;
else if (cmp > 0)
t = t.right;
// 2.1.1 找到相等节点,直接替代返回
else
return t.setValue(value);
} while (t != null);
}
// 3. 创建当前节点,并根据比较值,判断是为父类的左子节点,还是右子节点
Entry<K,V> e = new Entry<>(key, value, parent);
if (cmp < 0)
parent.left = e;
else
parent.right = e;
// 重新平衡
fixAfterInsertion(e);
size++;
modCount++;
return null;
}
有趣的问题:
TreeMap
的 key 是否能为 null?( value 能够为 null)
其实这与你的构造有关,本质上 TreeMap
需要排序,所以如果你提交了 Comparator
,并且它的实现不会抛出 null,那么 key 就可以为 null。但如果你依赖 key 实现 Comparable
接口来排序,那么 key 无论如何都不能为 null 了。
Map<String, String> baseMap = new TreeMap<>((o1, o2) -> 0);
// Map<String, String> baseMap = new TreeMap<>(); 会导致 put 时 抛出 NPE
baseMap.put(null, "1");
下面再来看 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) {
// Offload comparator-based version for sake of performance
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) {
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;
}
get 取值其实是一个在树中进行查找的操作,不过因为比较节点相等的方法不同(依赖比较器,或者实现了 Comparable 的 key),略有区分。
TreeMap
要么在构造时添加比较器,要么其 key 实现 Comparable 接口。
删除操作
remove 操作:
public V remove(Object key) {
// 找到当前节点
Entry<K,V> p = getEntry(key);
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.
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)
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);
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;
}
}
}
上面的删除节点代码,在理解了红黑树的删除实现之后,再来看会好懂一点。文末参考博文中会给出几篇比较好的学习红黑树算法的文章。
迭代器
迭代器的主要实现如下:
abstract class PrivateEntryIterator<T> implements Iterator<T> {
Entry<K,V> next;
Entry<K,V> lastReturned;
int expectedModCount;
PrivateEntryIterator(Entry<K,V> first) {
expectedModCount = modCount;
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();
// 找到后继(中序遍历)
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
if (lastReturned.left != null && lastReturned.right != null)
next = lastReturned;
deleteEntry(lastReturned);
expectedModCount = modCount;
lastReturned = null;
}
}
// 中序遍历:左、中、右
static <K,V> TreeMap.Entry<K,V> successor(Entry<K,V> t) {
if (t == null)
return null;
// t 刚好为 中 ,往右遍历
else if (t.right != null) {
Entry<K,V> p = t.right;
while (p.left != null)
p = p.left;
return p;
} else {
// t 刚好为 左,右,则往上
Entry<K,V> p = t.parent;
Entry<K,V> ch = t;
while (p != null && ch == p.right) {
ch = p;
p = p.parent;
}
return p;
}
}
复杂的在 successor
方法中,我们需要明白,它要做的事是中序遍历这颗树。
总结
TreeMap
是可排序的,它的排序方式有两种:
- 根据
Comparator
排序:如果比较器实现中,key 为 null 不会抛出异常,那么,TreeMap
中就可以存放 key 为 null 的映射。 - 根据 key 实现的
Comparable
接口:说明如果不提供比较器,则 key 必须实现Comparable
接口,并且在这样的比较下,key 是否为 null 的。
迭代器的设计与 HashMap
和 LinkedHashMap
一样,并且,都是属于快速失败的,在并发修改的时候,能够尽最大努力抛出 ConcurrentModificationException
。
TreeMap
完全是基于红黑树实现的(算法导论中的描述,略有修剪),文中并没有就红黑树再做出具体分析。
TreeMap
还实现了 NavigableMap
接口,该接口提供了一系列可导航的方法。
推荐博文
参考博文
我与风来
认认真真学习,做思想的产出者,而不是文字的搬运工
错误之处,还望指出