搞懂Java集合框架之TreeMap
简述
在Map集合框架中,除了HashMap以外,我们比较常用的还有TreeMap。HashMap是无序的,而TreeMap底层由红黑树实现,可以根据对应的排序规则对key进行排序。
TreeMap的继承关系图
红黑树
概述
- 红黑树是一种自平衡的二叉查找树
- 红黑树的每一个节点要么是红色,要么是黑色
- 红黑树是一种弱平衡的二叉树,它的平衡是由“红黑树的特性”实现的
特性
- 节点要么是红色,要么是黑色
- 根节点必须是黑色
- 叶子结点必须是黑色(nil)
- 不能出现两个红色节点相连的情况
- 对每一个节点,从该节点到其叶子结点的路径上,包含相同数目的叶子结点
图
源码分析
1、TreeMap的基本框架
下面是TreeMap比较重要的属性。
public class TreeMap<K,V>
extends AbstractMap<K,V>
implements NavigableMap<K,V>, Cloneable, java.io.Serializable
{
// 该比较器用于对这个TreeMap进行排序,如果为null,则按照自然排序规则排序
// final类型的变量必须在定义的时候,或者构造方法中赋值
private final Comparator<? super K> comparator;
// 根节点
private transient Entry<K,V> root;
// 节点个数
private transient int size = 0;
// 修改次数
private transient int modCount = 0;
// 无参构造
public TreeMap() {
comparator = null;
}
// 有参构造
public TreeMap(Comparator<? super K> comparator) {
this.comparator = comparator;
}
// 定义内部类表示键值对
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;
}
}
<? super K> 相当于 K 类型或者 K 的父类型。
2、get方法
public V get(Object key) {
// 调用方法根据key获取Entry对象
Entry<K,V> p = getEntry(key);
return (p==null ? null : p.value);
}
这里重点就是getEntry方法:首先判断是否有指定的比较器,如果有,就按比较器的排序进行查找;否则,按照自然顺序进行查找。
final Entry<K,V> getEntry(Object key) {
// 如果提供了比较器,就按比较器来进行查找
if (comparator != null)
return getEntryUsingComparator(key);
// TreeMap不允许key为null
if (key == null)
throw new NullPointerException();
// 把Object类型的key向下转型为Comparable
@SuppressWarnings("unchecked")
Comparable<? super K> k = (Comparable<? super K>) key;
// 从根节点开始查
Entry<K,V> p = root;
/**
* k.compareTo(p.key) 相当于 p.key - k
* 大于0 : p大于k,则需要从左子树查
* 小于0 : p小于k,则需要从右子树查
* 等于0 : 返回当前p节点
*/
while (p != null) {
// 调用Comparable的compareTo方法
int cmp = k.compareTo(p.key);
if (cmp < 0)
p = p.left;
else if (cmp > 0)
p = p.right;
else
return p;
}
// 如果到了叶子节点还没有找到需要找的key,返回null
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;
}
3、put方法
首先判断根节点是否为空,为空就将该元素设置为根节点。
如果根节点不为空,判断有没有自定义的比较器,如果有就按照自定义的比较器进行比较,否则就按照自然排序规则。一直到根节点,寻找对应插入的位置,如果在中途发现了相同的key,则进行值覆盖。否则,会在对应的叶子结点上添加元素,之后调用红黑树平衡调整的方法,使节点符合红黑树的规则,返回旧值(如果是新插入的,就返回空)。
public V put(K key, V value) {
Entry<K,V> t = root;
// 如果根节点为null,相当于树为空
if (t == null) {
// 对key进行非空和类型检验
compare(key, key); // type (and possibly null) check
root = new Entry<>(key, value, null);
size = 1;
// 记录集合修改次数
modCount++;
// 添加成功返回null,代表未覆盖值
return null;
}
int cmp;
Entry<K,V> parent;
Comparator<? super K> cpr = comparator;
//判断comparator是否为空
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); // 循环遍历到叶子节点为止
}
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);
}
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;
}
4、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;
}
小结
SortedMap
在遍历时严格按照key的顺序遍历,最常用的实现类就是TreeMap
- 作为
SortedMap
的key必须实现Comparable
接口,或者是传入Comparator
- 要严格按照
compare()
规范实现比较逻辑,否则,TreeMap
将不能正常工作