TreeMap是基于红黑树实现的,红黑树是一种特殊的二叉树,百科一下介绍红黑树的性质:
性质1. 节点是红色或黑色。
性质2. 根节点是黑色。
性质3 每个叶节点(NIL节点,空节点)是黑色的。
性质4 每个红色节点的两个子节点都是黑色。(从每个叶子到根的所有路径上不能有两个连续的红色节点)
性质5. 从任一节点到其每个叶子的所有路径都包含相同数目的黑色节点。
每次插入或者删除时,需要对二叉树进行调整,使得新树也满足红黑树限制。
构造函数 & 描述
1 TreeMap()
这个构造函数构造一个新的,空的树映射,使用键的自然顺序。
2 TreeMap(Comparator<? super K> comparator)
这个构造函数构造一个新的,空的树映射,根据给定的比较器进行排序。
3 TreeMap(Map<? extends K,? extends V> m)
这个构造函数构造一个新的树映射具有相同映射关系为给定的映射,根据其键的自然顺序进行排序。
4 TreeMap(SortedMap<K,? extends V> m)
SortedMap中的元素师有序的,这个构造函数构造一个新的树映射具有相同映射关系,并使用相同的顺序在指定的有序映射。
存储结构
TreeMap的排序是基于对key的排序实现的,它的每一个Node代表红黑树的一个节点,Node的数据结构如下:
static class Node<K, V> implements Map.Entry<K, V> {
//父节点
Node<K, V> parent;
//左节点
Node<K, V> left;
//右节点
Node<K, V> right;
//键
final K key;
//值
V value;
//高度
int height;
Node(Node<K, V> parent, K key) {
this.parent = parent;
this.key = key;
this.height = 1;
}
Node<K, V> copy(Node<K, V> parent) {
Node<K, V> result = new Node<K, V>(parent, key);
if (left != null) {
result.left = left.copy(result);
}
if (right != null) {
result.right = right.copy(result);
}
result.value = value;
result.height = height;
return result;
}
插入删除:
使用一组状态,把查找,创建,等操作进行封装,当root==null时,并且状态为CREATE,则创建一个新Node
public V put(K key, V value) {
return putInternal(key, value);
}
V putInternal(K key, V value) {
//查找该节点,并且创建一个新节点
Node<K, V> created = find(key, Relation.CREATE);
V result = created.value;
created.value = value;
return result;
}
Node<K, V> find(K key, Relation relation) {
if (root == null) {
if (comparator == NATURAL_ORDER && !(key instanceof Comparable)) {
throw new ClassCastException(key.getClass().getName() + " is not Comparable"); // NullPointerException ok
}
//创建一个新节点
if (relation == Relation.CREATE) {
root = new Node<K, V>(null, key);
size = 1;
modCount++;
return root;
} else {
return null;
}
}
@SuppressWarnings("unchecked")
Comparable<Object> comparableKey = (comparator == NATURAL_ORDER)
? (Comparable<Object>) key
: null;
Node<K, V> nearest = root;
while (true) {
//传入比较器则用传入的比较,否则用系统的compareTo比较,得到一个comparison值,用来觉得node在树中的位置
int comparison = (comparableKey != null)
? comparableKey.compareTo(nearest.key)
: comparator.compare(key, nearest.key);
if (comparison == 0) {
switch (relation) {
case LOWER:
//lower返回小于等于key,且最接近key得节点
//比如 1,2,3,5 key=4,没有找到4,返回最接近的一个值且小于等于4,所以返回key=3的值
return nearest.prev();
case FLOOR:
case EQUAL:
case CREATE:
case CEILING:
//返回key=该值得node
return nearest;
case HIGHER:
//higher返回大于等于key,且最接近key得节点
return nearest.next();
}
}
//comparsion<0则为左节点,comparsion>0则为右节点
Node<K, V> child = (comparison < 0) ? nearest.left : nearest.right;
if (child != null) {
nearest = child;
continue;
}
/*
* We found a nearest node. Every key not in the tree has up to two
* nearest nodes, one lower and one higher.
*/
if (comparison < 0) { // nearest.key is higher
switch (relation) {
case LOWER:
case FLOOR:
return nearest.prev();
case CEILING:
case HIGHER:
return nearest;
case EQUAL:
return null;
case CREATE:
//创建一个新节点,指向nearest的左节点
Node<K, V> created = new Node<K, V>(nearest, key);
nearest.left = created;
size++;
modCount++;
//调整二叉树保持红黑树特性
rebalance(nearest, true);
return created;
}
} else { // comparison > 0, nearest.key is lower
switch (relation) {
case LOWER:
case FLOOR:
return nearest;
case CEILING:
case HIGHER:
return nearest.next();
case EQUAL:
return null;
case CREATE:
//创建一个新节点,指向nearest的右节点
Node<K, V> created = new Node<K, V>(nearest, key);
nearest.right = created;
size++;
modCount++;
//调整二叉树保持红黑树特性
rebalance(nearest, true);
return created;
}
}
}
}
插入操作都是同个find方法完成,通过上面分析,基本上清楚了是怎么插入元素的,剩下rebalance方法这里不分析,是红黑树旋转问题,主要是调整二叉树保持平衡。
public void remove() {
if (last == null) {
throw new IllegalStateException();
}
//删除一个节点
removeInternal(last);
expectedModCount = modCount;
last = null;
}
void removeInternal(Node<K, V> node) {
//先记录下要删除节点左右节点,及父节点
Node<K, V> left = node.left;
Node<K, V> right = node.right;
Node<K, V> originalParent = node.parent;
if (left != null && right != null) {
//有左右子节点情况
//找出相邻节点,比较左右子树高度,选择高度较高子树
// 1 1
// / \ / \
// *2* 3 ---》 4 3
// / \ / / \ /
// 4 5 6 7 5 6
// /
//7
// 假设选中2节点删除,2的左子树高度比较高,那么2相邻的子节点,adjacent 就为4,反之则为5
Node<K, V> adjacent = (left.height > right.height) ? left.last() : right.first();
removeInternal(adjacent); // takes care of rebalance and size--
int leftHeight = 0;
left = node.left;
if (left != null) {
//断开node节点与左子节点连接
leftHeight = left.height;
adjacent.left = left;
left.parent = adjacent;
node.left = null;
}
int rightHeight = 0;
right = node.right;
if (right != null) {
//断开node节点与右子节点连接
rightHeight = right.height;
adjacent.right = right;
right.parent = adjacent;
node.right = null;
}
adjacent.height = Math.max(leftHeight, rightHeight) + 1;
//断开node节点与父节点连接,并且让相邻节点adjacent,连接上node节点的父节点
replaceInParent(node, adjacent);
return;
} else if (left != null) {
//该节点只有左子节点,把子节点置空
replaceInParent(node, left);
node.left = null;
} else if (right != null) {
//该节点只有右子节点,把子节点置空
replaceInParent(node, right);
node.right = null;
} else {
replaceInParent(node, null);
}
rebalance(originalParent, false);
size--;
modCount++;
}
删除方法比较绕,阅读的时候最好是画图理解,remove中剩下replaceInParent方法,接下来就看下它做了什么:
private void replaceInParent(Node<K, V> node, Node<K, V> replacement) {
//先把node父节点保存到临时变量,然后断开与父节点的连接
Node<K, V> parent = node.parent;
node.parent = null;
//如果相邻节点不为null,那么相邻节点父节点指向原node节点的父节点
if (replacement != null) {
replacement.parent = parent;
}
//node父节点不为null,那么node不是root节点
if (parent != null) {
//node节点是左节点,那么相邻节点就替换node父节点的左子节点
if (parent.left == node) {
parent.left = replacement;
} else {
//相邻节点就替换node父节点的右子节点
// assert (parent.right == node);
parent.right = replacement;
}
} else {
//node为root节点,故相邻节点直接替换root
root = replacement;
}
}
删除操作分析完了,其他操作就不再分析,这里总结几点:
1、TreeMap的查询、插入、删除效率均没有HashMap高,每次插入删除,如果破坏了红黑树平衡,需要对树旋转操作,一般只有要对key排序时才使用TreeMap。
2、TreeMap的key不能为null,而HashMap的key可以为null。
3、TreeMap是根据key进行排序的,排序和定位需要依赖比较器或覆写Comparable接口,因此不需要key覆写hashCode方法和equals方法,就可以排除掉重复的key,而HashMap的key则需要通过覆写hashCode方法和equals方法来确保没有重复的key。