本文基于jdk1.8进行分析。
TreeMap继承自AbstractMap 实现了NavigableMap接口,意味着它支持一系列的导航方法。比如返回有序的key集合。
此外其还实现了Cloneable, java.io.Serializable两个接口说明其是可以被克隆、序列化的。
TreeMap基于红黑树(Red-Black tree)实现。该映射根据其键的自然顺序进行排序,或者根据创建映射时提供的Comparator 进行排序,具体取决于使用的构造方法。
【1】核心属性和构造
① 核心属性
//比较器,因为TreeMap是有序的,通过comparator接口我们可以对TreeMap的内部排序进行精密的控制
//可以通过构造函数自定义比较器,也可以使用默认比较器
private final Comparator<? super K> comparator;
//treeMap的根节点
private transient Entry<K,V> root = null;
//容器大小
private transient int size = 0;
//结构修改次数
private transient int modCount = 0;
//红黑树结构
private static final boolean RED = false;
private static final boolean BLACK = true;
//继承自父类AbstractMap
transient Set<K> keySet;
transient Collection<V> values;
② 构造函数
默认构造函数,比较器为null。
public TreeMap() {
comparator = null;
}
指定比较器comparator
public TreeMap(Comparator<? super K> comparator) {
this.comparator = comparator;
}
使用给定Map实例化TreeMap
public TreeMap(Map<? extends K, ? extends V> m) {
comparator = null;
putAll(m);
}
使用一个SortedMap初始化TreeMap,这里会同时绑定比较器与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) {
}
}
③ 核心结点类型Entry
如下所示Entry是TreeMap的静态内部类实现了Map.Entry<K,V>
接口。其除了key value外还维护了left、right、parent以及color(红还是黑)。这里没有看到prev、next表明**其底层数据结构是红黑树而非数组+链表**
。
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;//父节点
// BLACK默认值是true
boolean color = BLACK;
Entry(K key, V value, Entry<K,V> parent) {
this.key = key;
this.value = value;
this.parent = parent;
}
//...
}
【2】获取方法
① get(Object key)
如下所示其通过getEntry(key)
方法获取到P,然后返回p.value。
public V get(Object key) {
Entry<K,V> p = getEntry(key);
return (p==null ? null : p.value);
}
② getEntry
如果比较器不为null,则将行为交给getEntryUsingComparator
方法实现。否则从root结点开始与目标key进行compareTo对比。
- 如果
k.compareTo(p.key)<0
,说明目标key小于对比结点p,则p=p.left; - 如果
k.compareTo(p.key)>0
,说明目标key大于于对比结点p,则p=p.right; - 如果
k.compareTo(p.key)==0
,说明目标key等于对比结点p,直接返回p; - 否则,返回null。
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;
// 如果p == null ,直接返回null
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;
}
③ getEntryUsingComparator
这个方法核心在于使用比较器来对比两个结点的key,而非采用目标对象的compareTo方法。本质仍然是对比,判断left还是right,更新p,进行下一步判断,直到找到合适的结点。否则就返回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;
}
④ getFirstEntry&getLastEntry
如下所示,其一路遍历找到最底层的left结点。也就是排序结果最小的一个结点(红黑树是二叉查找树哦)。
final Entry<K,V> getFirstEntry() {
Entry<K,V> p = root;
if (p != null)
while (p.left != null)
p = p.left;
return p;
}
与之对应的则是getLastEntry会一路找到最底层的right结点,也就是排序最大的结点。
final Entry<K,V> getLastEntry() {
Entry<K,V> p = root;
if (p != null)
while (p.right != null)
p = p.right;
return p;
}
⑤ getHigherEntry
返回大于目标K key的元素中最小的一个。
比如从(1 2 3 4 5)中找到大于目标key=2的元素中最小的为3,如果找不到则返回null。其实也就是找结点2的后继结点。
final Entry<K,V> getHigherEntry(K key) {
Entry<K,V> p = root;
while (p != null) {
//compare方法要么采用比较器,要么采用对象自身的compareTo方法
int cmp = compare(key, p.key);
if (cmp < 0) {
if (p.left != null)
p = p.left;
else
return p;
} else {
if (p.right != null) {
p = p.right;
} else {
// 这里为什么不是返回null呢?给机会回溯,
//能够找到其父节点作为后继结点返回
Entry<K,V> parent = p.parent;
Entry<K,V> ch = p;
//这里ch == parent.right很关键
while (parent != null && ch == parent.right) {
ch = parent;
parent = parent.parent;
}
return parent;
}
}
}
//否则就返回null
return null;
}
与之对应的则是getLowerEntry。仍旧以(1 2 3 4 5)为例,找到比3小的元素中最大的元素也就是2。
getLowerEntry本质就是找到当前结点的前驱结点,可能为null。
final Entry<K,V> getLowerEntry(K key) {
Entry<K,V> p = root;
while (p != null) {
int cmp = compare(key, p.key);
// k > p 往p的右侧找
if (cmp > 0) {
if (p.right != null)
p = p.right;
else
return p;
} 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;
}
}
}
return null;
}
【3】存放方法
① put
方法逻辑梳理如下:
- 判断根节点是否为null
- 如果比较器不为null,则使用比较器进行对比,尝试从left、right找到合适的结点(位置)
- 如果比较器为null,则使用对象自身的compareTo方法从根节点往下找到合适的结点(位置)
- 插入结点并进行处理
- 更新size、modCount
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;
}
// 记录key比较之后的大小
int cmp;
Entry<K,V> parent;
// split comparator and comparable paths
Comparator<? super K> cpr = comparator;
//如果比较器不为null,使用比较器从left right找结点(位置)
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);
}
// 如果比较器为null,则使用对象自身的compareTo方法寻找
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);
}
// 到这里找到了当前parent cmp
Entry<K,V> e = new Entry<>(key, value, parent);
//插入e
if (cmp < 0)
parent.left = e;
else
parent.right = e;
//这个很重要 会触发左旋和右旋
fixAfterInsertion(e);
//更新size 、modCount
size++;
modCount++;
return null;
}
其结点结构只有left、right以及parent,没有prev、next也就是典型的树结构。遍历查找合适结点(位置)其实也就是树的遍历。这里特别需要注意的是插入后的操作,会触发红黑树的左旋和右旋。
② fixAfterInsertion
下面注释中符号释义:
- xp是x.parent,也就是父亲结点
- xpp 为xp的parent,也就是祖父结点
- xppl 为xpp的left,可能是XP也可能是叔叔结点;
- xppr 为 xpp的right ,可能是XP也可能是叔叔结点
- 这个过程会涉及到红黑树的左旋和右旋。
private void fixAfterInsertion(Entry<K,V> x) {
x.color = RED;
//xp.color ==red
while (x != null && x != root && x.parent.color == RED) {
// xp==xp.parent.left 也就是左侧分支
if (parentOf(x) == leftOf(parentOf(parentOf(x)))) {
// y=xppr
Entry<K,V> y = rightOf(parentOf(parentOf(x)));
//xppr.color=red
if (colorOf(y) == RED) {
//xp.color=black
setColor(parentOf(x), BLACK);
//xppr.color=black
setColor(y, BLACK);
//xpp.color=red
setColor(parentOf(parentOf(x)), RED);
// x=xpp
x = parentOf(parentOf(x));
} else {
//x=xpr
if (x == rightOf(parentOf(x))) {
// 等价于rotateLeft(x=xp)
x = parentOf(x);
rotateLeft(x);
}
// xp.color=black
setColor(parentOf(x), BLACK);
// xpp.color=red
setColor(parentOf(parentOf(x)), RED);
//rotateRight(x)
rotateRight(parentOf(parentOf(x)));
}
} else {
//右侧分支 y=xppl
Entry<K,V> y = leftOf(parentOf(parentOf(x)));
if (colorOf(y) == RED) {//叔叔是红色,XP是红色,那么必然进行颜色调整
setColor(parentOf(x), BLACK);
setColor(y, BLACK);
setColor(parentOf(parentOf(x)), RED);
x = parentOf(parentOf(x));
} else {//可能是RL调整,也可能是RR调整
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;
}
其实总结起来就是六种情况:
- 左侧分支,叔叔是红色,此时不发生结构调整,只需要改变父亲与叔叔结点颜色为黑色;
- 右侧分支,叔叔是红色,此时不发生结构调整,只需要改变父亲与叔叔结点颜色为黑色;
- LL调整
- LR调整
- RL调整
- RR调整
整体来讲这个过程其实就是红黑树的平衡过程。具体流程图示可以参考博文:认真研究HashMap中的平衡插入
另外和HashMap一样,删除结点后也要进行平衡。fixAfterDeletion
方法可以参考HashMap的balanceDeletion
方法。
参考博文:认真学习数据结构之红黑树