TreeMap的key允许为空
刚开始学习时就被告知treemap的key不能为空!!!
但是 随着学习的深入,自己读源码发现允许为空 大家详细研读此文,本文末尾列举treemap的key为空的案例
TreeMap底层采用红黑树(R-B Tree) 数据结构,所以我们先了解红黑树部分逻辑结构在研读TreeMap底层代码。
红黑树是特殊的二叉查找树,意味着它满足二叉查找树的特征:任意一个节点所包含的键值,大于等于左孩子的键值,小于等于右孩子的键值。
除了具备该特性之外,红黑树还包括许多额外的信息。
红黑树的特性:(其他如左旋右旋等不详细赘述)
红黑树数据结构实现代码详解
(1) 每个节点或者是黑色,或者是红色。
(2) 根节点是黑色。
(3) 每个叶子节点是黑色。 注意:这里叶子节点,是指为空的叶子节点!
(4) 如果一个节点是红色的,则它的子节点必须是黑色的。
(5) 从一个节点到该节点的子孙节点的所有路径上包含相同数目的黑节点。
一:红黑树节点的结构设计
因为红黑树是根据数据大小插入到数中,所以必须要实现 Comparable 接口,确定存入数据可比较(在TreeMap中可以选择指定比较器)
public class RBTNode<T extends Comparable<T>> {
boolean color; // 颜色
T key; // 关键字(键值)
RBTNode<T> left; // 左孩子
RBTNode<T> right; // 右孩子
RBTNode<T> parent; // 父结点
public RBTNode(T key, boolean color, RBTNode<T> parent, RBTNode<T> left, RBTNode<T> right) {
this.key = key;
this.color = color;
this.parent = parent;
this.left = left;
this.right = right;
}
public T getKey() {
return key;
}
}
二:TreeMap与HashMap实现接口区别
// TreeMap 额外实现 NavigableMap 接口继承自 SortedMap 继承自 Map 额外增加的方法
lowerEntry //返回所有比给定Map.Entry小的元素
floorEntry //返回所有比给定Map.Entry小或相等的元素
ceilingEntry //返回所有比给定Map.Entry大或相等的元素
higherEntry //返回所有比给定Map.Entry大的元素
三:TreeMap的成员变量
- private final Comparator< ? super K > comparator; //比较器(不指定比较器需要保证key 实现Comparator接口 否则会抛出异常)
- private transient Entry< K,V > root; // 红黑树的根节点
- private transient int size = 0; //树的节点个数
- private transient int modCount = 0; // 操作次数(添加节点,删除节点)
四:TreeMap的构造方法
/**
* 空的构造方法
* Key值必须实现Comparator接口
* 否则会抛出异常
*/
public TreeMap() {
comparator = null;
}
/**
* 参数为Comparator接口
* 指定该TreeMap对应的比较器
*/
public TreeMap(Comparator<? super K> comparator) {
this.comparator = comparator;
}
/**
* 参数为Map 将原来的Map传入新的TreeMap中
* key值需实现Comparator接口
*/
public TreeMap(Map<? extends K, ? extends V> m) {
comparator = null;
putAll(m);
}
四:TreeMap的put方法
/**
* 传入key-value键值对
*/
public V put(K key, V value) {
// 地址指向根节点
Entry<K,V> t = root;
// 若根节点为空 把该节点当个成根节点
if (t == null) {
// 若 key为null 且为指定Comparator接口 则会抛出异常
compare(key, key); // type (and possibly null) check
// 为根节点分配内存
root = new Entry<>(key, value, null);
size = 1;
modCount++;
return null;
}
// 存放比较结果
int cmp;
// 存放当前节点的父节点
Entry<K,V> parent;
// split comparator and comparable paths
// 获取指定的比较器
Comparator<? super K> cpr = comparator;
// 若获取的比较器不为空
if (cpr != null) {
// 获取到该节点该存放位置
// 将 t 指向该位置
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 {
// 若key为空
if (key == null)
throw new NullPointerException();
@SuppressWarnings("unchecked")
// 提取key实现的Comparable接口
Comparable<? super K> k = (Comparable<? super K>) key;
// 获取到该节点该存放位置
// 将 t 指向该位置
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;
}
五:TreeMap的get方法
/**
* 传入key 返回value
* 若不存在key 返回null
*/
public V get(Object key) {
Entry<K,V> p = getEntry(key);
return (p==null ? null : p.value);
}
final Entry<K,V> getEntry(Object key) {
// Offload comparator-based version for sake of performance
// 若比较器不为空
if (comparator != null)
return getEntryUsingComparator(key);
// 若key值为空
if (key == null)
throw new NullPointerException();
@SuppressWarnings("unchecked")
Comparable<? super K> k = (Comparable<? super K>) key;
Entry<K,V> p = root;
while (p != null) {
// 存放比较结果
int cmp = cpr.compare(k, p.key);
// 若比较结果小于0 p指向p的右节点
if (cmp < 0)
p = p.left;
// 若比较结果大于0 p指向p的左节点
else if (cmp > 0)
p = p.right;
// 若相等 返回当前p节点地址
else
return p;
}
// 不存在 返回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;
// 只要p下面还有子节点 就继续执行
while (p != null) {
// 存放比较结果
int cmp = cpr.compare(k, p.key);
// 若比较结果小于0 p指向p的右节点
if (cmp < 0)
p = p.left;
// 若比较结果大于0 p指向p的左节点
else if (cmp > 0)
p = p.right;
// 若相等 返回当前p节点地址
else
return p;
}
}
return null;
}
源码阅读到此结束:以下是我对TreeMap自己的一些理解
- TreeMap的key不能为null否则会抛出异常,读过源码的我要说!!!这个错误的,TreeMap的key允许为空,当你指定比较器时你可以为key=null指定你希望的值,HashMap的key也可以为null。
TreeMap<String, String> map = new TreeMap<String, String>((e1, e2) -> {
if (e1 == null) {
return Integer.MAX_VALUE;
} else if (e2 == null) {
return Integer.MAX_VALUE;
} else {
return Integer.compare(e1.length(), e2.length());
}
});
map.put("a", "一个a");
map.put("aa", "两个a");
map.put("aaa", "三个a");
map.put("aaaa", "四个a");
map.put("aaaaa", "五个a");
map.put(null, "无数个a");
// Lambda表达式 不理解点击下面链接
// 可以用你理解的方式遍历
map.forEach((k, v) -> System.out.println(k + v));
2. TreeMap是根据key进行排序的,它的排序和定位需要依赖比较器或覆写Comparable接口,也因此不需要key覆写hashCode方法和equals方法,就可以排除掉重复的key,而HashMap的key则需要通过覆写hashCode方法和equals方法来确保没有重复的key。
3. TreeMap的查询、插入、删除效率均没有HashMap高,一般只有要对key排序时才使用TreeMap。
下节预告 LinkedHashmap