概念
TreeMap是键值对按键有序,为了实现Key有序,要求Key实现Comparable接口或者构造函数传入Comparator来保证Key的顺序。
相对于HashMap实现Map接口,TreeMap还实现了SortedMap和NavigationMap进一步提供总体顺序Map方法和扩展一些方法lowerXX,floorXX,ceilingXX,higherXX等导航方法来方便TreeMap操作。
源码解析
TreeMap底层原理实现是红黑树。首先红黑树一颗二叉搜索树或者叫二叉排序树,也就是其左子树,父节点,右子树保持一种相对有序的状态。因此在二叉搜索树中查找数据相对比较高效(我们每次只需折半查找就行),但是二叉搜索树在数据极端的情况下,就会退化成链表,也就散失了二叉搜索树查找的高效性,为了保持这种高效查找的特性,我们就必须维持二叉树搜索树的左右子树的相对平衡性。因此前辈们就发明了很多维持树平衡的算法如: AVL树,红黑树等。红黑树是通过着色和树旋转来保持二叉树的相对平衡,这里为了分析代码的连贯性和简洁性,我们对于红黑树通过着色和旋转来调整二叉树的平衡操作不进行展开分析。 除去这些TreeMap的主要实现也就是二叉搜索树的实现过程分析了。
因为源码涉及到数据结构树等相关知识,这些知识需要读者自己去了解其基本概念和一些步骤,否则我不保证你们能看的懂。
字段属性
// 比较器,比较Key的顺序
private final Comparator<? super K> comparator;
// 二叉搜索树的根节点
private transient Entry<K,V> root = null;
// 树的节点数量
private transient int size = 0;
二叉搜索树的节点结构
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;
// 红黑树节点标识
boolean color = BLACK;
// 节点构造函数
Entry(K key, V value, Entry<K,V> parent) {
this.key = key;
this.value = value;
this.parent = parent;
}
}
方法分析
以下代码只涉及核心逻辑,判断和其他其余条件都去除了。
put
// 插入键值对
public V put(K key, V value) {
// 根节点
Entry<K,V> t = root;
// 根节点为空构建一个新节点作为根节点
if (t == null) {
root = new Entry<K,V>(key, value, null);
size = 1;
modCount++;
return null;
}
int cmp;
Entry<K,V> parent;
// L0: 遍历查找插入节点的父节点。
Comparator<? super K> cpr = comparator;
if (cpr != null) {
do {
// 从根节点元素开始从上往下遍历二叉树搜索树
parent = t;
// 假定使用的是comparator
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);
}
// L1: 将新节点插入二叉树中
Entry<K,V> e = new Entry<K,V>(key, value, parent);
if (cmp < 0)
// 小于父节点,插入到父节点的左边
parent.left = e;
else
// 大于父节点,插入到父节点的右边
parent.right = e;
// 因为插入操作会破坏红黑树的特性,所以需要进行着色和旋转进行修正
// L2: 重新着色和旋转修正红黑树。
fixAfterInsertion(e);
size++;
return null;
}
remove
// 根据键删除键值对
public V remove(Object key) {
// 遍历查找key相关的节点。
Entry<K,V> p = getEntry(key);
if (p == null)
return null;
// 删除key相关的节点,并返回原来的值。
V oldValue = p.value;
deleteEntry(p);
return oldValue;
}
// 删除二叉树搜索树指定节点
private void deleteEntry(Entry<K,V> p) {
size--;
// L0:两个节点情况:如果删除节点有左右孩子,则将删除结点的后继节点替换成删除结点,转化成删除节点只有一个子孩子或者没有孩子的情况。
if (p.left != null && p.right != null) {
// 寻找删除节点的后继节点
Entry<K,V> s = successor(p);
// 替换删除节点
p.key = s.key;
p.value = s.value;
p = s;
}
// 待删节点的孩子节点(因为经过L0步骤已经将删除节点具有两个节点的情况转化成了一个节点的情况或者没有孩子的情况)
Entry<K,V> replacement = p.left != null ? p.left : p.right;
// L1: 一个节点情况, 将待删节点的父节点和其孩子进行链接。
if (replacement != null) {
if (p.parent == null) {
// 删除的节点是根节点
root = replacement;
} else if (p == p.parent.left) {
// 删除的节点是父节点的左孩子
p.parent.left = replacement;
} else {
// 删除的节点是父节点的右孩子
p.parent.right = replacement;
}
// 删除节点置null.
p.left = p.right = p.parent = null;
// 删除节点的颜色是黑色,破坏了红黑树第五条特性,重新着色或者旋转进行修正。
if (p.color == BLACK) {
fixAfterDeletion(replacement);
}
} else if (p.parent == null) {
// L2: 根节点情况
// 只有一个节点情况。
root == null;
} else {
// L3: 叶子节点情况
// 删除的是叶子节点的情况,破坏了红黑树第五条特性,重新着色或者旋转进行修正。
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;
}
// 将待删节点的父节点置null。
p.parent = null;
}
}
}
// 寻找后继节点
static <K,V> Entry<K,V> successor(Entry<K,V> t) {
// 这里直接给出寻找后继节点的算法步骤,具体原因不展开
// 查找一个节点的后继节点(如果这个节点有右子树,则后继节点是右子树中最小的节点,否则是第一个非右孩子节点的父节点)。
if (t == null)
return null;
// 如果右子树中存在左子树,最小节点一定存在于右子树的左子树中,如果不存在左子树,则最小节点是右子树的第一个右孩子。
else if (t.right != null) {
Entry<K,V> p = t.right;
while (p.left != null) {
p = p.left;
}
return p;
}
// 不存在右子树,则后继节点是第一个非右孩子的父节点。
else (t.parent != null) {
Entry<K,V> p = t.parent;
Entry<K,V> ch = t;
while (p != null && ch == p.right) {
ch = p;
p = p.parent;
}
return p;
}
}
get
// 根据键查找键值对
public V get(Object key) {
Entry<K,V> p = getEntry(key);
return (p==null ? null : p.value);
}
// 根据key在二叉搜索树中查找相关节点
final Entry<K,V> getEntry(Object key) {
Entry<K,V> p = root;
int cmp;
// 从根节点元素开始从上往下遍历二叉树搜索树
while (p != null) {
cmp = comparator.compare(key, p.key)
if (cmp < 0) {
// 小于父节点,左子树查找。
p = p.left;
} else if (cmp > 0) {
// 大于父节点,右子树查找。
p = p.right;
} else {
// 找到key所对应的节点
return p;
}
}
// 没找到返回null
return null;
}
// 查找第一个节点,也就是最小key值节点,肯定是存在与最左子树中
final Entry<K,V> getFirstEntry() {
Entry<K,V> p = root;
if (p != null)
while (p.left != null)
p = p.left;
return p;
}
// 查找最后一个节点,也就是最大key值节点,肯定是存在与最右子树中
final Entry<K,V> getLastEntry() {
Entry<K,V> p = root;
if (p != null)
while (p.right != null)
p = p.right;
return p;
}