JDK容器学习之TreeMap (一) : 底层数据结构
时间:2022-04-27
本文章向大家介绍JDK容器学习之TreeMap (一) : 底层数据结构,主要内容包括TreeMap、2. 添加一个kv对、小结、相关博文、关注更多、基本概念、基础应用、原理机制和需要注意的事项等,并结合实例形式分析了其使用技巧,希望通过本文能帮助到大家理解应用这部分内容。
TreeMap
在日常的工作中,相比较与
HashMap
而言,TreeMap
的使用会少很多,即使在某些场景,需要使用到排序的Map时,也更多的是选择LinkedHashMap
,那么这个TreeMap
到底是个怎样的容器,又适用于什么样的应用场景呢?
1. 数据结构分析
分析数据结构,最好的方式无疑是google+baidu+源码了
1. 继承体系
看到源码第一眼,就会发现与HashMap不同的是 TreeMap 实现的是 NavigableMap
, 而不是直接实现 Map
public class TreeMap<K,V>
extends AbstractMap<K,V>
implements NavigableMap<K,V>, Cloneable, java.io.Serializable {
// ....
}
有必要仔细看下这个 NavigableMap
,到底有些什么特殊之处
继承体系: Map -> SortMap -> NavigbleMap
其中 SortMap
新增了下面几个接口,目前也不知道具体有啥用,先翻译下源码注释
// 既然叫做SortMap, 要排序的话,当然需要一个比较器了
Comparator<? super K> comparator();
SortedMap<K,V> subMap(K fromKey, K toKey);
// 源码注释: 返回比Map中key比参数toKey小的所有kv对
SortedMap<K,V> headMap(K toKey);
// 源码注释:返回比Map中key比参数fromKey大或相等的所有kv对
SortedMap<K,V> tailMap(K fromKey);
K firstKey();
K lastKey();
接着就是 NavigableMap
定义的接口
// 返回Map中比传入参数key小的kv对中,key最大的一个kv对
Map.Entry<K,V> lowerEntry(K key);
K lowerKey(K key);
// 返回Map中比传入参数key小或相等的kv对中,key最大的一个kv对
Map.Entry<K,V> floorEntry(K key);
K floorKey(K key);
// 返回Map中比传入参数key大或相等的kv对中,key最小的一个kv对
Map.Entry<K,V> ceilingEntry(K key);
K ceilingKey(K key);
// 返回Map中比传入参数key大的kv对中,key最小的一个kv对
Map.Entry<K,V> higherEntry(K key);
K higherKey(K key);
Map.Entry<K,V> firstEntry();
Map.Entry<K,V> lastEntry();
Map.Entry<K,V> pollFirstEntry();
NavigableMap<K,V> descendingMap();
NavigableSet<K> navigableKeySet();
NavigableSet<K> descendingKeySet();
基本上这两个接口就是提供了一些基于排序的获取kv对的方式
2. 数据结构
看下内部的成员变量,发现可能涉及到数据结构的就只有下面的这个root了
private transient Entry<K,V> root;
结合 TreeMap
的命名来看,底层的结构多半就真的是Tree
了,有树的根节点,一般来讲遍历都是没啥问题的
接下来看下 Entry的实现
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;
/**
* Make a new cell with given key, value, and parent, and with
* {@code null} child links, and BLACK color.
*/
Entry(K key, V value, Entry<K,V> parent) {
this.key = key;
this.value = value;
this.parent = parent;
}
/**
* Returns the key.
*
* @return the key
*/
public K getKey() {
return key;
}
/**
* Returns the value associated with the key.
*
* @return the value associated with the key
*/
public V getValue() {
return value;
}
/**
* Replaces the value currently associated with the key with the given
* value.
*
* @return the value associated with the key before this method was
* called
*/
public V setValue(V value) {
V oldValue = this.value;
this.value = value;
return oldValue;
}
public boolean equals(Object o) {
if (!(o instanceof Map.Entry))
return false;
Map.Entry<?,?> e = (Map.Entry<?,?>)o;
return valEquals(key,e.getKey()) && valEquals(value,e.getValue());
}
public int hashCode() {
int keyHash = (key==null ? 0 : key.hashCode());
int valueHash = (value==null ? 0 : value.hashCode());
return keyHash ^ valueHash;
}
public String toString() {
return key + "=" + value;
}
}
从Entry的内部成员变量可以看出,这是一个二叉树,且极有可能就是一颗红黑树(因为有个black
)
2. 添加一个kv对
通过新增一个kv对的调用链,来分析下这棵树,到底是不是红黑树
将put方法捞出来, 然后补上注释
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;
}
int cmp;
Entry<K,V> parent;
// split comparator and comparable paths
Comparator<? super K> cpr = comparator;
if (cpr != null) {
// 下面这个循环可以得出树的左节点小于根小于右节点
// 然后找到新增的节点,作为叶子节点在树中的位置
// 注意这个相等时,直接更新了value值(这里表示插入一条已存在的记录)
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继承 Comparable 接口
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节点
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;
}
从添加逻辑,可以得出结论:
- 树结构为二叉排序树(且不能出现相等的情况)
- 重排的方法可以保证该树为红黑树
所以新增一个kv对的逻辑就比较简单了
遍历树,将kv对作为叶子节点存在对应的位置
小结
红黑树相关可以作为独立的一个知识点,这里不详细展开,基本上通过上面的分析,可以得出下面几个点
- TreeMap 底层结构为红黑树
- 红黑树的Node排序是根据Key进行比较
- 每次新增删除节点,都可能导致红黑树的重排
- 红黑树中不支持两个or已上的Node节点对应
红黑值
相等
相关博文
- JDK容器学习之HashMap (一) : 底层存储结构分析
- JDK容器学习之HashMap (二) : 读写逻辑详解
- JDK容器学习之HashMap (三) : 迭代器实现
关注更多
扫一扫二维码,关注 小灰灰blog
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- php数组指针函数功能及用法示例
- thinkphp5框架路由原理与用法详解
- ThinkPHP5&5.1实现验证码的生成、使用及点击刷新功能示例
- php反序列化长度变化尾部字符串逃逸(0CTF-2016-piapiapia)
- 对python 命令的-u参数详解
- ThinkPHP5.1+Ajax实现的无刷新分页功能示例
- Python推导式简单示例【列表推导式、字典推导式与集合推导式】
- Python 从相对路径下import的方法
- Python随机生成身份证号码及校验功能
- 对python的bytes类型数据split分割切片方法
- PHP For循环字母A-Z当超过26个字母时输出AA,AB,AC
- python 实现数字字符串左侧补零的方法
- tp5.0框架隐藏index.php入口文件及模块和控制器的方法分析
- laravel框架使用FormRequest进行表单验证,验证异常返回JSON操作示例
- 使用Python实现微信提醒备忘录功能