TreeMap 源码解读
TreeMap
下文中提到的比较大小,>0,<0 都是指key用比较器比较大小
简介
TreeMap是一个有序的Map,是基于红黑树实现的。排序根据Map创建时传入的Comaprable或Comparator实现排序,如果没有传,则默认按插入数据的顺序。
TreeMap 的get,containsKey,get,put,remove 操作使用的时间为log(n),因为每次查找都是从树的根节点开始一层一层向下查找,
TreeMap也是非线程安全的,所以,如果想使用线程安全的sorted map 可以用如下代码获取
SortedMap m = Collections.synchronizedSortedMap(new TreeMap(...))
和大多数非线程安全的集合一样,如果在迭代器创建后修改了TreeMap的结构,会抛出ConcurrentModificationException异常。
内部有一个比较器Comparator comparator 维护TreeMap的内部数据顺序,如果比较器为空,那么TreeMap的存储顺序就按插入的顺序存储。
有一个根节点 Entry root 内部包含父节点,左子节点,右子节点 颜色是否是黑色。entry是TreeMap中的元素节点,所有这些节点按red-black tree 排列,形成了TreeMap。
size 记录当前map中节点的个数
modCount 记录Treemap的结构性修改。
部分代码讲解
- getEntry
final Entry<K,V> getEntry(Object key) {
// Offload comparator-based version for sake of performance
if (comparator != null)
return getEntryUsingComparator(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 = k.compareTo(p.key);
if (cmp < 0)
p = p.left;// 向左找
else if (cmp > 0)
p = p.right; // 向右找
else
return p;
}
return null;
}
上面这段代码的意思,从根节点开始找,如果比较值<0 ,证明当前节点的key大了(按照comparable比较),
则向树的左子节点查找,如果比较值>0,则向树的右子节点上查找。知道查找到为止。
其中函数中getEntryUsingComparator()的比较思路也是这样。
- getCeilingEntry
// 获取当前值对应的entry,如果没有找到,则返回在map中比key大一点的entry
final Entry<K,V> getCeilingEntry(K key) {
Entry<K,V> p = root;
while (p != null) {
int cmp = compare(key, p.key);
// 如果p比查找的节点大
if (cmp < 0) {
// 如果当前节点的左子节点不为空,向这个节点的左边查找
if (p.left != null)
p = p.left;
else
// 如果当前节点的左子节点为空,那么比查找值大一点的就是p这个节点
return p;
} else if (cmp > 0) { // 如果p比查找的节点小。向右查找
if (p.right != null) { // 如果p的右子节点不为空,继续向有查找
p = p.right;
} else {
// 如果右节点为空,这就麻烦了,要从p网上找,直到父节点为空
// 或者找到了父节点的右子节点不在p到父(父...)节点的路径上
// 拿笔在纸上画一画就明白了
Entry<K,V> parent = p.parent;
Entry<K,V> ch = p;
while (parent != null && ch == parent.right) {
ch = parent;
parent = parent.parent;
}
return parent;
}
} else
return p;
}
return null;
}
和上面的逻辑类似,寻找给定key对应的entry,如果没找到,则返回比给定key小的最大的key对应的entry
- getFloorEntry
final Entry<K,V> getFloorEntry(K key) {
Entry<K,V> p = root;
while (p != null) { // 如果根节点为空直接返回null
int cmp = compare(key, p.key);
if (cmp > 0) { // 如果p.key比给定的key小,则向右找
if (p.right != null)
p = p.right;
else
return p;
} else if (cmp < 0) { // 如果p.key比给定的key大,则向左找
if (p.left != null) {
p = p.left;
} else {
// 如果p的左子节点为空,那么就要一直往上找,一直找到一个一个节点,其左子节点,
// 一直找到一个节点,不在p到其父(父...)节点的连线上。
Entry<K,V> parent = p.parent;
Entry<K,V> ch = p;
while (parent != null && ch == parent.left) {
ch = parent;
parent = parent.parent;
}
return parent;
}
} else
return p;
}
return null;
}
下面到了最精彩的地方了,那就是put方法,这是TreeMap的核心,
本质上是向红黑树上插入一个节点。建议先看一下红黑树插入数据的操作,这样会很容易明白这段逻辑。
put的核心就是代码中最后的fixAfterInsertion(e)
- 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) { // 如果比较器不为空,根据比较器比较key的大小,确定要插入entry的存放位置
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); // 如果找到了之前存放过该key的节点,直接替换其中的值
} 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);
}
// 如果上面没有查找到,这时候树上的指针已经移动到parent节点,
// 当前key与parent节点比较大小确定新增的节点放到左子节点还是右子节点
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;
}
未完,待续
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 智能桌面机器人:用.NET IoT库控制舵机并多方法播放表情
· Linux glibc自带哈希表的用例及性能测试
· 深入理解 Mybatis 分库分表执行原理
· 如何打造一个高并发系统?
· .NET Core GC压缩(compact_phase)底层原理浅谈
· 新年开篇:在本地部署DeepSeek大模型实现联网增强的AI应用
· DeepSeek火爆全网,官网宕机?本地部署一个随便玩「LLM探索」
· Janus Pro:DeepSeek 开源革新,多模态 AI 的未来
· 上周热点回顾(1.20-1.26)
· 【译】.NET 升级助手现在支持升级到集中式包管理