一、类定义

TreeMap的类结构:

public class TreeMap<K,V> extends AbstractMap<K,V> implements NavigableMap<K,V>, Cloneable, java.io.Serializable
  • TreeMap 是一个有序的key-value集合,它是通过红黑树实现的。
  • TreeMap 继承于AbstractMap,所以它是一个Map,即一个key-value集合。
  • TreeMap 实现了NavigableMap接口,意味着它支持一系列的导航方法。比如返回有序的key集合。
  • TreeMap 实现了Cloneable接口,意味着它能被克隆。
  • TreeMap 实现了java.io.Serializable接口,意味着它支持序列化。

二、成员变量、构造函数

    // 用于接收外部比较器,插入时用于比对元素的大小
private final Comparator<? super K> comparator;
// 红黑树的根节点
private transient Entry<K,V> root;
// 树中元素个数
private transient int size = 0; private transient int modCount = 0;
// 默认构造函数。使用该构造函数,TreeMap中的元素按照自然排序进行排列。
TreeMap() // 创建的TreeMap包含Map
TreeMap(Map<? extends K, ? extends V> copyFrom) // 指定Tree的比较器
TreeMap(Comparator<? super K> comparator) // 创建的TreeSet包含copyFrom
TreeMap(SortedMap<K, ? extends V> copyFrom)

TreeMap跟HashMap等都是一样,内部的节点用Entry所表示,来看看TreeMap的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; // 其他省略
}

三、源码解析

TreeMap的实现是基于红黑树的结构,那么啥是红黑树?什么是红黑树

1、put()

    // 将“key, value”添加到TreeMap中
// 理解TreeMap的前提是掌握“红黑树”。
// 若理解“红黑树中添加节点”的算法,则很容易理解put。
public V put(K key, V value) {
Entry<K,V> t = root;
// 若红黑树为空,则插入根节点
if (t == null) {
root = new Entry<K,V>(key, value, null);
size = 1;
modCount++;
return null;
}
int cmp;
Entry<K,V> parent;
Comparator<? super K> cpr = comparator;
// 在二叉树(红黑树是特殊的二叉树)中,找到(key, value)的插入位置。
// 红黑树是以key来进行排序的,所以这里以key来进行查找。
if (cpr != null) {
do {
//记录当前的父节点,从根节点开始
parent = t;
cmp = cpr.compare(key, t.key);
//插入的key小于当前父节点的key,继续比对left节点
if (cmp < 0)
t = t.left;
//右节点赋值给t,继续循环
else if (cmp > 0)
t = t.right;
else
//key值相等,将value覆盖之前的value
return t.setValue(value);
//当t为null,说明已经是最后一个节点了
} while (t != null);
}
else {
if (key == null)
throw new NullPointerException();
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);
}
// 新建红黑树的节点(e)
Entry<K,V> e = new Entry<K,V>(key, value, parent);
if (cmp < 0)
parent.left = e;
else
parent.right = e;
// 红黑树插入节点后,不再是一颗红黑树;
// 这里通过fixAfterInsertion的处理,来恢复红黑树的特性。例如左旋、右旋、变色等等,这块比较复杂,有兴趣的可以自己看源码。初步接触可以理解恢复红黑树的特性就好。
fixAfterInsertion(e);
//新加一个节点,size自然+1
size++;
modCount++;
return null;
}

2、get()

    //根据给定的key值获取到TreeMap中的节点,然后取这个节点的value值。
public V get(Object key) {
Entry<K,V> p = getEntry(key);
return (p==null ? null : p.value);
}
final Entry<K,V> getEntry(Object key) {
//如果有比较器的话,走比较器的比较方法,去寻找到对应的节点
if (comparator != null)
return getEntryUsingComparator(key);
if (key == null)
throw new NullPointerException();
@SuppressWarnings("unchecked")
//如果不是使用Comparator参数构造器初始化TreeMap的话,往TreeMap中插入的key必须要实现Comparable接口
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;
}
//这个在put()里面已经介绍过
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、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;
}
private void deleteEntry(Entry<K,V> p) {
modCount++;
size--;
//情况一:待删除的节点有两个孩子
if (p.left != null && p.right != null) {
//找右子树中最小元素节点
Entry<K,V> s = successor(p);
p.key = s.key;
p.value = s.value;
p = s;
}
Entry<K,V> replacement = (p.left != null ? p.left : p.right); //情况二:待删除节点只有一个孩子
if (replacement != null) {
replacement.parent = p.parent;
if (p.parent == null)
root = replacement;
else if (p == p.parent.left)
p.parent.left = replacement;
else
p.parent.right = replacement; p.left = p.right = p.parent = null; if (p.color == BLACK)
fixAfterDeletion(replacement);
}
//情况三:根节点
else if (p.parent == null) {
root = null;
}
//情况四:无任何孩子节点
else {
if (p.color == BLACK)
fixAfterDeletion(p); if (p.parent != null) {
if (p == p.parent.left)
p.parent.left = null;
else if (p == p.parent.right)
p.parent.right = null;
p.parent = null;
}
}
}
static <K,V> TreeMap.Entry<K,V> successor(Entry<K,V> t) {
if (t == null)
return null;
else if (t.right != null) {// 找右子树中最小元素节点
Entry<K,V> p = t.right;
while (p.left != null)
p = p.left;
return p;
} else {// 下面这段代码根本走不到,因为deleteEntry在调用此方法时传过来的t非null
Entry<K,V> p = t.parent;
Entry<K,V> ch = t;
while (p != null && ch == p.right) {
ch = p;
p = p.parent;
}
return p;
}
}

删除的流程有点复杂,解释如下:

红黑树的删除分两步:
(1)以二叉查找树的方式删除节点。
(2)恢复红黑树的性质。

删除分为四种情况:
1、树只有根节点:直接删除即可。
2、待删除节点无孩子:直接删除即可。
3、待删除节点只有一个孩子节点:删除后,用孩子节点替换自己即可。
4、待删除节点有两个孩子:删除会复杂点。

针对第一种情况以及第二种情况,很简单,直接删;第三种情况也比较简单,只是多一个步骤,删除了当前节点之后,用孩子节点替换自己,自己离任,得找个接班人;第四种情况如下图所示:

假设我们要删除节点2:

首先找到右子树最小的节点,然后将此节点与要删除的节点对换,对换不会影响二叉树的特性,这里分析一下,5节点的左子节点以及该字节点所有的孩子节点都会比5小,并且5节点的最小左节点肯定是大于要删除的节点2,因为二叉查找树的特性决定。

可以更简单的理解,任何一个节点,它的左节点底下所有的节点都会比它小,它的右节点下的所有节点都会比它大。

走进JDK(十二)------TreeMap的更多相关文章

  1. 走进JDK(十)------HashMap

    有人说HashMap是jdk中最难的类,重要性不用多说了,敲过代码的应该都懂,那么一起啃下这个硬骨头吧!一.哈希表在了解HashMap之前,先看看啥是哈希表,首先回顾下数组以及链表数组:采用一段连续的 ...

  2. Java之集合(十二)TreeMap

    转载请注明源出处:http://www.cnblogs.com/lighten/p/7411935.html 1.前言 本章介绍Map体系中的TreeMap,顾名思义,这个是一个树结构的Map.Tre ...

  3. 十二、jdk工具之jcmd介绍(堆转储、堆分析、获取系统信息、查看堆外内存)

    目录 一.jdk工具之jps(JVM Process Status Tools)命令使用 二.jdk命令之javah命令(C Header and Stub File Generator) 三.jdk ...

  4. JAVA基础知识总结:一到二十二全部总结

    >一: 一.软件开发的常识 1.什么是软件? 一系列按照特定顺序组织起来的计算机数据或者指令 常见的软件: 系统软件:Windows\Mac OS \Linux 应用软件:QQ,一系列的播放器( ...

  5. 201871010123-吴丽丽《面向对象程序设计(Java)》第十二周学习总结

    201871010123-吴丽丽<面向对象程序设计(Java)>第十二周学习总结 项目 内容 这个作业属于哪个课程  https://www.cnblogs.com/nwnu-daizh/ ...

  6. 201871010106-丁宣元 《面向对象程序设计(java)》第十二周学习总结

    201871010106-丁宣元 <面向对象程序设计(java)>第十二周学习总结 正文开头: 项目 内容 这个作业属于哪个课程 https://home.cnblogs.com/u/nw ...

  7. 201871010111-刘佳华《面向对象程序设计(java)》第十二周学习总结

    201871010111-刘佳华<面向对象程序设计(java)>第十二周学习总结 实验十   集合与GUI初步 实验时间 2019-11-14 第一部分:基础知识总结 第九章知识总结 1. ...

  8. 201871010105-曹玉中《面向对象程序设计(java)》第十二周学习总结

    201871010105-曹玉中<面向对象程序设计(java)>第十二周学习总结 项目 内容 这个作业属于哪个课程 https://www.cnblogs.com/nwnu-daizh/ ...

  9. 201871010134-周英杰 《面向对象程序设计(java)》第十二周学习总结

    201871010134-周英杰 <面向对象程序设计(java)>第十二周学习总结 项目 内容 这个作业属于哪个课程 https://www.cnblogs.com/nwnu-daizh/ ...

随机推荐

  1. ajax、json、jsonp

    这章分享下ajax.json.jsonp的学习记录,不得不说这真是些令人激动的技术. 推荐文章: https://segmentfault.com/a/1190000012469713 http:// ...

  2. 区块链入门(5)Truffle 项目实战,Solidity IDE, 智能合约部署

    在上一张我们学习了Truffle项目的创建,部署等相关内容,今天我们就来实战一下. 今天我们要做3件事: 1) 学习搭建一个Solidity IDE(Remix). 2) 使用这个Solidity I ...

  3. FM(Factorization Machines)模型详解

    优点 FM模型可以在非常稀疏的数据中进行合理的参数估计,而SVM做不到这点 在FM模型的复杂度是线性的,优化效果很好,而且不需要像SVM一样依赖于支持向量. FM是一个通用模型,它可以用于任何特征为实 ...

  4. jquery 中dataTable显示加载中,图片或文字

    引入js文件 <script type="text/javascript" src="${basePath}/lib/datatables/1.10.0/jquer ...

  5. 【转载】chown和chmod使用

    二.指令名称 : chown 使用权限 : root 使用方式 : chown [-cfhvR] [--help] [--version] user[:group] file... 说明 : Linu ...

  6. Git冲突标记介绍

    <<<<<<< head 是指你本地的分支的 <<<<<<< HEADb789=======b45678910> ...

  7. Linux 学习笔记 3:Shell 基础

    1.Shell的工作原理 Shell是用户和操作系统之间的接口.当用户登录系统,有一个Shell进程随之启动,并在用户注销时终止.用户输入的命令是Shell输入. 处理命令的步骤:查找命令中的元字符: ...

  8. debian9使用国内源安装docker以及一些使用方法

    debian9使用国内源安装docker以及一些使用方法   首先, 我的环境是debian, 容器是centos debian 扔源 # deb-src [arch=amd64] https://m ...

  9. 生信分析常用脚本(二)--SOAPdenovo

    1.SOAPDenovo配置文件示例 软件下载安装和使用:http://soap.genomics.org.cn/soapdenovo.html asm.cfg #maximal read lengt ...

  10. luogu||P1776||宝物筛选||多重背包||dp||二进制优化

    题目描述 终于,破解了千年的难题.小FF找到了王室的宝物室,里面堆满了无数价值连城的宝物……这下小FF可发财了,嘎嘎.但是这里的宝物实在是太多了,小FF的采集车似乎装不下那么多宝物.看来小FF只能含泪 ...