TreeMap解读
TreeMap解读
引言:
- TreeMap基于红黑树实现,是有序的key-value集合
- TreeMap 实现了NavigableMap接口,意味着它支持一系列的导航方法比如返回有序的key集合。
- TreeMap 实现了Cloneable接口,意味着它能被克隆。
- TreeMap 实现了java.io.Serializable接口,意味着它支持序列化。
- TreeMap基于红黑树(Red-Black tree)实现。该映射根据其键的自然顺序进行排序,或者根据创建映射时提供的 Comparator 进行排序,具体取决于使用的构造方法。
- TreeMap的基本操作 containsKey、get、put 和 remove 的时间复杂度是 log(n) 。
- TreeMap非同步,即线程不安全,迭代器是fail-fast机制。
一、构造器以及方法
- 构造器
// 默认构造函数。使用该构造函数,TreeMap中的元素按照自然排序进行排列。
TreeMap()
// 创建的TreeMap包含Map
TreeMap(Map<? extends K, ? extends V> copyFrom)
// 指定Tree的比较器
TreeMap(Comparator<? super K> comparator)
// 创建的TreeMap包含copyFrom
TreeMap(SortedMap<K, ? extends V> copyFrom)
- 暴露方法
Entry<K, V> ceilingEntry(K key)
K ceilingKey(K key)
void clear()
Object clone()
Comparator<? super K> comparator()
boolean containsKey(Object key)
NavigableSet<K> descendingKeySet()
NavigableMap<K, V> descendingMap()
Set<Entry<K, V>> entrySet()
Entry<K, V> firstEntry()
K firstKey()
Entry<K, V> floorEntry(K key)
K floorKey(K key)
V get(Object key)
NavigableMap<K, V> headMap(K to, boolean inclusive)
SortedMap<K, V> headMap(K toExclusive)
Entry<K, V> higherEntry(K key)
K higherKey(K key)
boolean isEmpty()
Set<K> keySet()
Entry<K, V> lastEntry()
K lastKey()
Entry<K, V> lowerEntry(K key)
K lowerKey(K key)
NavigableSet<K> navigableKeySet()
Entry<K, V> pollFirstEntry()
Entry<K, V> pollLastEntry()
V put(K key, V value)
V remove(Object key)
int size()
SortedMap<K, V> subMap(K fromInclusive, K toExclusive)
NavigableMap<K, V> subMap(K from, boolean fromInclusive, K to, boolean toInclusive)
NavigableMap<K, V> tailMap(K from, boolean inclusive)
SortedMap<K, V> tailMap(K fromInclusive)
二、继承图
在代码上的体现为:
java.lang.Object
↳ java.util.AbstractMap<K, V>
↳ java.util.TreeMap<K, V>
public class TreeMap<K,V>
extends AbstractMap<K,V>
implements NavigableMap<K,V>, Cloneable, java.io.Serializable {}
由上,得出:
- TreeMap实现继承于AbstractMap,并且实现了NavigableMap接口
- TreeMap的本质是R-B Tree(红黑树),它包含几个重要的成员变量: root, size, comparator。
- root 是红黑数的根节点。它是Entry类型,Entry是红黑数的节点,它包含了红黑数的6个基本组成成分:key(键)、value(值)、left(左孩子)、right(右孩子)、parent(父节点)、color(颜色)。Entry节点根据key进行排序,Entry节点包含的内容为value。 红黑数排序时,根据Entry中的key进行排序;Entry中的key比较大小是根据比较器comparator来进行判断的。size是红黑数中节点的个数。
红黑树的具体实现看我的下一期博文!
三、TreeMap内部结构
先罗列内部数据结构的概况:
- 核心:红黑树实现
- 完善:比较器、迭代器、排序方式等等
1、Entry
下面开始从源码解读TreeMap,首先是节点: Entry
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 K getKey() {
return key;
}
public V getValue() {
return value;
}
//代替value
public V setValue(V value) {
V oldValue = this.value;
this.value = value;
return oldValue;
}
//key相同、value相同即为=
public boolean equals(Object o) {
if (!(o instanceof Map.Entry))
return false;
Map.Entry<?,?> e = (Map.Entry<?,?>)o;
return valEquals(key,e.getKey()) && valEquals(value,e.getValue());
}
//根据key和value共同计算hashCode
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;
}
}
小结:
- TreeMap内部的树节点是Entry,该类的成员有key(键)、value(值)、left(左孩子)、right(右孩子)、parent(父节点)、color(颜色),其中,颜色默认都是黑色(BLACK)。
- Entry其实就是红黑树节点。
2、基本操作与实现
① 红黑树操作
//左旋
private void rotateLeft(Entry<K,V> p) { ... }
//右旋
private void rotateRight(Entry<K,V> p) { ... }
//插入
public V put(K key, V value) { ... }
//插入修正,确保插入节点后该树还是红黑树
private void fixAfterInsertion(Entry<K,V> x) { ... }
//删除
private void deleteEntry(Entry<K,V> p) { ... }
//删除修正
private void fixAfterDeletion(Entry<K,V> x) { ... }
② 构造器底层实现
// 带Map的构造函数,Map会成为TreeMap的子集
public TreeMap(Map<? extends K, ? extends V> m) {
comparator = null;
putAll(m);
}
// 带SortedMap的构造函数,SortedMap会成为TreeMap的子集
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) {
}
}
// 默认构造函数
public TreeMap() {
comparator = null;
}
// 带比较器的构造函数
public TreeMap(Comparator<? super K> comparator) {
this.comparator = comparator;
}
上面的构造器的实现并不难理解,其中通过Map去创建treeMap,则是将map中的元素全部添加到treeMap中去
public void putAll(Map<? extends K, ? extends V> m) {
for (Map.Entry<? extends K, ? extends V> e : m.entrySet())
//通过遍历添加
put(e.getKey(), e.getValue());
}
而通过有序的Sortedmap去构造TreeMap,则是调用 buildFromSorted
,以下贴出源码:
// 根据已经一个排好序的map创建一个TreeMap
// 将map中的元素逐个添加到TreeMap中,并返回map的中间元素作为根节点。
//递归调用之后,返回一个middle节点作为treeMap的root节点
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) / 2;
Entry<K,V> left = null;
// 若lo小于mid,则递归调用获取(middel的)左孩子。
if (lo < mid) //mid -1
left = buildFromSorted(level+1, lo, mid - 1, redLevel,
it, str, defaultVal);
// 获取middle节点对应的key和value
K key;
V value;
if (it != null) {
if (defaultVal==null) {
Map.Entry<K,V> entry = (Map.Entry<K,V>)it.next();
key = entry.getKey();
value = entry.getValue();
} else {
key = (K)it.next();
value = defaultVal;
}
} else { // use stream
key = (K) str.readObject();
value = (defaultVal != null ? defaultVal : (V) str.readObject());
}
// 创建middle节点
Entry<K,V> middle = new Entry<K,V>(key, value, null);
// 若当前节点的深度=红色节点的深度,则将节点着色为红色。
if (level == redLevel)
middle.color = RED;
// 设置middle为left的父亲,left为middle的左孩子
if (left != null) {
middle.left = left;
left.parent = middle;
}
if (mid < hi) {
// 递归调用获取(middel的)右孩子。 //mid+1
Entry<K,V> right = buildFromSorted(level+1, mid+1, hi, redLevel,
it, str, defaultVal);
// 设置middle为left的父亲,left为middle的左孩子
middle.right = right;
right.parent = middle;
}
return middle;
}
buildFromSorted的理解:
- buildFromSorted是通过递归将SortedMap中的元素逐个关联。
- buildFromSorted返回middle节点(中间节点)作为root。
- buildFromSorted**添加到红黑树中时,只将level == redLevel的节点设为红色。**第level级节点,实际上是buildFromSorted转换成红黑树后的最底端(假设根节点在最上方)的节点;只将红黑树最底端的阶段着色为红色,其余都是黑色。
③ 相关entry操作
TreeMap的 firstEntry()、 lastEntry()、 lowerEntry()、 higherEntry()、 floorEntry()、 ceilingEntry()、 pollFirstEntry() 、 pollLastEntry() 原理都是类似的;下面以firstEntry()来进行详细说明
我们先看看firstEntry()和getFirstEntry()的代码:
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;
}
这里我们有一个疑问? firstEntry()与getFirstEntry()都是获取第一个节点,为什么要多此一举呢?
目的: 防止用户修改返回的entry,即返回的entry是不可修改的。
既然知道原因了,那么接下来我们就从源码中探索它是怎么实现的。
I.getFirstEntry()
final Entry<K,V> getFirstEntry();
返回的是一个entry,我们可以调用entry内部的 getKey()、getValue()来获取key和value值,以及调用setValue()来修改value的值。
II. firstEntry()
firstEntry()返回的是exportEntry封装后的entry,我们看看是怎么封装的。
static <K,V> Map.Entry<K,V> exportEntry(TreeMap.Entry<K,V> e) {
return e == null? null :
new AbstractMap.SimpleImmutableEntry<K,V>(e);
}
//SimpleImmutableEntry为AbstractMap的静态内部类
public static class SimpleImmutableEntry<K,V>
implements Entry<K,V>, java.io.Serializable
{
private static final long serialVersionUID = 7138329143949025153L;
private final K key;
private final V value;
public SimpleImmutableEntry(K key, V value) {
this.key = key;
this.value = value;
}
public SimpleImmutableEntry(Entry<? extends K, ? extends V> entry) {
this.key = entry.getKey();
this.value = entry.getValue();
}
public K getKey() {
return key;
}
public V getValue() {
return value;
}
//如果强行调用setValue则会抛出不支持的异常。
public V setValue(V value) {
throw new UnsupportedOperationException();
}
public boolean equals(Object o) {
if (!(o instanceof Map.Entry))
return false;
Map.Entry e = (Map.Entry)o;
return eq(key, e.getKey()) && eq(value, e.getValue());
}
public int hashCode() {
return (key == null ? 0 : key.hashCode()) ^
(value == null ? 0 : value.hashCode());
}
public String toString() {
return key + "=" + value;
}
}
根据上述源码,我们可以懂得:
- 通过AbstractMap的Sim…对entry节点进行封装,对外只暴露封装后的entry,只有getKey、getValue操作。
- 强行执行Set,则会抛出不支持的异常。
④ 相关key操作
在解读TreeMap中,我不按照解读HashMap或者List之类集合的方式,而是采用 方法名相关性进行解读,即XXKey(),XXEntry()等操作进行分类解读。好了,继续解读!
TreeMap的**firstKey()、lastKey()、lowerKey()、higherKey()、floorKey()、ceilingKey()**原理都是类似的;下面以ceilingKey()来进行详细说明
ceilingKey(K key)的作用是“返回大于/等于key的最小的键值对所对应的KEY,没有的话返回null”,它的代码如下:
public K ceilingKey(K key) {
return keyOrNull(getCeilingEntry(key));
}
static <K,V> K keyOrNull(TreeMap.Entry<K,V> e) {
return e == null? null : e.key;
}
final Entry<K,V> getCeilingEntry(K key) {
Entry<K,V> p = root;
while (p != null) {
int cmp = compare(key, p.key);
// 情况一:若“p的key” > key。
// 若 p 存在左孩子,则设 p=“p的左孩子”;
// 否则,返回p
if (cmp < 0) {
if (p.left != null)
p = p.left;
else
return p;
// 情况二:若“p的key” < key。
} else if (cmp > 0) {
// 若 p 存在右孩子,则设 p=“p的右孩子”
if (p.right != null) {
p = p.right;
} else {
// 若 p 不存在右孩子,则找出 p 的后继节点,并返回
// 注意:这里返回的 “p的后继节点”有2种可能性:第一,null;第二,TreeMap中大于key的最小的节点。
// 理解这一点的核心是,getCeilingEntry是从root开始遍历的。
// 若getCeilingEntry能走到这一步,那么,它之前“已经遍历过的节点的key”都 > key。
// 能理解上面所说的,那么就很容易明白,为什么“p的后继节点”有2种可能性了。
Entry<K,V> parent = p.parent;
Entry<K,V> ch = p;
while (parent != null && ch == parent.right) {
ch = parent;
parent = parent.parent;
}
return parent;
}
// 情况三:若“p的key” = key。
} else
return p;
}
return null;
}
⑤ 相关Values操作
values() 返回**“TreeMap中值的集合”**,values()的实现代码如下:
public Collection<V> values() {
//封装成collection
Collection<V> vs = values;
return (vs != null) ? vs : (values = new Values());
}
// ”TreeMap的值的集合“对应的类,它集成于AbstractCollection
class Values extends AbstractCollection<V> {
// 返回迭代器
public Iterator<V> iterator() {
return new ValueIterator(getFirstEntry());
}
// 返回个数
public int size() {
return TreeMap.this.size();
}
// "TreeMap的值的集合"中是否包含"对象o"
public boolean contains(Object o) {
return TreeMap.this.containsValue(o);
}
// 删除"TreeMap的值的集合"中的"对象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;
}
// 清空删除"TreeMap的值的集合"
public void clear() {
TreeMap.this.clear();
}
}
小结:我们可以知道Values类就是一个集合。而 AbstractCollection 实现了除 size() 和 iterator() 之外的其它函数,因此只需要在Values类中实现这两个函数即可。这一部分内容可以类比我的上一篇解读hashMap的values、keySet,基本上的实现原理是一样的,都是采用内部类将key、value封装成集合并且返回。
⑥ 迭代器、entrySet等的操作
采用内部类将key、value封装成集合并且返回,而迭代器则是封装了treeMap的节点迭代遍历的实现,从而称为迭代器。这部分比较简单,如果想要深入了解这一部分操作的读者,可以参考我的博文中关于 hashMap的解读,其中有详细讲解。
⑦ NavigableSubMap与反向TreeMap(descendingMap)
*先讲作用:*它是一个抽象集合类,为2个子类——"(升序)AscendingSubMap"和"(降序)DescendingSubMap"而服务;因为NavigableSubMap实现了许多公共API。它的最终目的是实现下面的一系列函数:
headMap(K toKey, boolean inclusive)
headMap(K toKey)
subMap(K fromKey, K toKey)
subMap(K fromKey, boolean fromInclusive, K toKey, boolean toInclusive)
tailMap(K fromKey)
tailMap(K fromKey, boolean inclusive)
navigableKeySet()
descendingKeySet()
descendingMap:
descendingMap() 的作用是返回当前TreeMap的反向的TreeMap。所谓反向,就是排序顺序和原始的顺序相反。
我们已经知道TreeMap是一颗红黑树,而红黑树是有序的。TreeMap的排序方式是通过比较器,在创建TreeMap的时候,若指定了比较器,则使用该比较器;否则,就使用Java的默认比较器。而获取TreeMap的反向TreeMap的原理就是将比较器反向即可,即获取反向比较器。从而将整个treeMap反向排序。
四、总结
TreeMap的源码解读其实很好理解,只要抓住几个核心:
- TreeMap的重要核心是红黑树数据结构,基于红黑树的底层实现。
- TreeMap与HashMap的迭代器、键、值集的实现都是采用内部类的方法,其中迭代器也是封装了遍历过程
- TreeMap是有序的键值集合,通过comparator比较器实现顺序的比较,而比较器其实就是一个排序的规则而已,我们自己也可以实现比较器从而按照我们的规则排序treeMap。
- TreeMap的反向map的原理就是获取反向规则的比较器,再进行排序,得到反向规则下的treeMap。
最后补充说明,如果读者想要深入了解TreeMap的底层,建议关注我的博文——《红黑树解读与Java实现》