一、概述

  TreeMap是基于红黑树实现的。由于TreeMap实现了java.util.sortMap接口,集合中的映射关系是具有一定顺序的,该映射根据其键的自然顺序进行排序或者根据创建映射时提供的Comparator进行排序,具体取决于使用的构造方法。另外TreeMap中不允许键对象是null。

  1、什么是红黑树?

  红黑树是一种特殊的二叉排序树,主要有以下几条基本性质:

  • 每个节点都只能是红色或者黑色
  • 根节点是黑色
  • 每个叶子节点是黑色的
  • 如果一个节点是红色的,则它的两个子节点都是黑色的
  • 从任意一个节点到每个叶子节点的所有路径都包含相同数目的黑色节点

  红黑树的具体原理分析和算法设计可参见博文:红黑树的原理分析和算法设计

  2、key的两种排序方式

  自然排序:TreeMap的所有key必须实现Comparable接口,并且所有key应该是同一个类的对象,否则将会抛ClassCastException异常

  指定排序:这种排序需要在构造TreeMap时,传入一个Comparator对象,该对象负责对TreeMap中的key进行排序

  3、TreeMap类的继承关系

public class TreeMap<K,V> 

  extends AbstractMap<K,V>

    implements NavigableMap<K,V>, Cloneable, Serializable

  其中,NavigableMap接口是扩展的SortMap,具有了针对给定搜索目标返回最接近匹配项的导航方法。其方法 lowerEntryfloorEntryceilingEntryhigherEntry 分别返回与小于、小于等于、大于等于、大于给定键的键关联的 Map.Entry 对象,如果不存在这样的键,则返回 null。类似地,方法 lowerKeyfloorKeyceilingKeyhigherKey 只返回关联的键。所有这些方法是为查找条目而不是遍历条目而设计的。

二、TreeMap源码分析

  1、存储结构

  TreeMap是基于红黑树实现的,树的节点定义如下:

 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;
//构造函数
Entry(K key, V value, Entry<K,V> parent)
{
this.key = key;
this.value = value;
this.parent = parent;
}
......
}

  2、构造函数

  TreeMap有四种构造函数,分别对应不同的参数。

   //1.使用键的自然顺序构造一个新的、空的树映射
public TreeMap()
{
comparator = null;
}
//2.构造一个新的、空的树映射,该映射根据给定比较器进行排序
public TreeMap(Comparator<? super K> comparator)
{
this.comparator = comparator;
}
/3.构造一个与给定映射具有相同映射关系的新的树映射,该映射根据其键的自然顺序 进行排序
public TreeMap(Map<? extends K, ? extends V> m)
{
comparator = null;
putAll(m);
}
//4.构造一个与指定有序映射具有相同映射关系和相同排序顺序的新的树映射
public TreeMap(SortedMap<K, ? extends V> m)
{
comparator = m.comparator();
try
{
buildFromSorted(m.size(), m.entrySet().iterator(), null, null);
}
catch (java.io.IOException cannotHappen)
{
}
catch (ClassNotFoundException cannotHappen)
{
}
}

  3、TreeMap常用方法

  V put(K key,V value):将键值对(key,value)添加到TreeMap中

public V put(K key, V value)
{
Entry<K,V> t = root;
//若根节点为空,则以(key,value)为参数新建节点
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)
{
do {
parent = t;
cmp = cpr.compare(key, t.key);
if (cmp < 0) //表示新增节点的key小于当前及节点的key,则以当前节点的左子节点作为新的当前节点
t = t.left;
else if (cmp > 0) //表示新增节点的key大于当前及节点的key,则以当前节点的右子节点作为新的当前节点
t = t.right;
else
return t.setValue(value); //相等则覆盖旧值
} while (t != null);
}
//如果cpr为空,则采用默认的排序算法进行创建TreeMap集合
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);
}
//将新增节点当做parent的子节点
Entry<K,V> e = new Entry<>(key, value, parent);
if (cmp < 0)
parent.left = e;
else
parent.right = e;
//插入新的节点后,调用fixAfterInsertion调整红黑树
fixAfterInsertion(e);
size++;
modCount++;
return null;
}

  Set<Map.Entry<K,V>> entrySet():返回此映射中包含的映射关系的Set视图

    public Set<Map.Entry<K,V>> entrySet()
{
EntrySet es = entrySet;
return (es != null) ? es : (entrySet = new EntrySet());
}

  boolean remove(Object o): 如果此 TreeMap 中存在该键的映射关系,则将其删除

    public boolean remove(Object o)
    {
if (!(o instanceof Map.Entry))
return false;
Map.Entry<K,V> entry = (Map.Entry<K,V>) o;
K key = entry.getKey();
if (!inRange(key))
return false;
TreeMap.Entry<K,V> node = m.getEntry(key);
if (node!=null && valEquals(node.getValue(),
entry.getValue()))
{
m.deleteEntry(node);
return true;
}
return false;
}
    }

三、TreeMap应用示例代码

public class TreeMapDemo
{
public static void main(String[] args)
{
//使用键的自然顺序构造一个新的、空的树映射
TreeMap<String,String> tm=new TreeMap<>();
tm.put("001", "中国");
tm.put("003", "美国");
tm.put("002", "法国");
System.out.println("调用entrySet得到键值对集:");
Set<Entry<String, String>> result=tm.entrySet();
for(Entry<String, String> result2:result)
{
System.out.println(result2.getKey()+"---"+result2.getValue());
}
System.out.println("调用keySet得到键集:");
Set<String> result3=tm.keySet();
for(String str:result3)
{
System.out.println(str);
}
System.out.println("调用values得到值集:");
Collection result4=tm.values();
for(Object str:result4)
System.out.println(str); //新建一个带比较器的TreeMap
TreeMap<String,String> tm2=new TreeMap<>(new ComparatorDemo());
tm2.put("001", "中国");
tm2.put("003", "美国");
tm2.put("002", "法国");
Set<Entry<String, String>> result5=tm2.entrySet();
for(Entry<String, String> result2:result5)
{
System.out.println(result2.getKey()+"---"+result2.getValue());
}
}
}

  首先按照键的自然顺序构建TreeMap,加入元素并遍历:

  

  然后新建一个比较器类,实现Comparator接口

public class ComparatorDemo implements Comparator<String>
{ public int compare(String o1, String o2) {
return 1;
} }

  在带比较器的tm2中,按照与tm1相同的顺序添加元素,此时再遍历tm2,结果如下:

  

  这里的顺序是按照比较器的compare方法得来的。由于compare方法总是返回1,即大小均相同,所以tm2中key顺序为初始添加顺序。    

Java集合之TreeMap源码分析的更多相关文章

  1. 死磕 java集合之TreeMap源码分析(四)-内含彩蛋

    欢迎关注我的公众号"彤哥读源码",查看更多源码系列文章, 与彤哥一起畅游源码的海洋. 二叉树的遍历 我们知道二叉查找树的遍历有前序遍历.中序遍历.后序遍历. (1)前序遍历,先遍历 ...

  2. 死磕 java集合之TreeMap源码分析(一)- 内含红黑树分析全过程

    欢迎关注我的公众号"彤哥读源码",查看更多源码系列文章, 与彤哥一起畅游源码的海洋. 简介 TreeMap使用红黑树存储元素,可以保证元素按key值的大小进行遍历. 继承体系 Tr ...

  3. 死磕 java集合之TreeMap源码分析(三)- 内含红黑树分析全过程

    欢迎关注我的公众号"彤哥读源码",查看更多源码系列文章, 与彤哥一起畅游源码的海洋. 删除元素 删除元素本身比较简单,就是采用二叉树的删除规则. (1)如果删除的位置有两个叶子节点 ...

  4. 死磕 java集合之TreeMap源码分析(二)- 内含红黑树分析全过程

    欢迎关注我的公众号"彤哥读源码",查看更多源码系列文章, 与彤哥一起畅游源码的海洋. 插入元素 插入元素,如果元素在树中存在,则替换value:如果元素不存在,则插入到对应的位置, ...

  5. 死磕 java集合之DelayQueue源码分析

    问题 (1)DelayQueue是阻塞队列吗? (2)DelayQueue的实现方式? (3)DelayQueue主要用于什么场景? 简介 DelayQueue是java并发包下的延时阻塞队列,常用于 ...

  6. 死磕 java集合之PriorityBlockingQueue源码分析

    问题 (1)PriorityBlockingQueue的实现方式? (2)PriorityBlockingQueue是否需要扩容? (3)PriorityBlockingQueue是怎么控制并发安全的 ...

  7. 死磕 java集合之PriorityQueue源码分析

    问题 (1)什么是优先级队列? (2)怎么实现一个优先级队列? (3)PriorityQueue是线程安全的吗? (4)PriorityQueue就有序的吗? 简介 优先级队列,是0个或多个元素的集合 ...

  8. 死磕 java集合之CopyOnWriteArraySet源码分析——内含巧妙设计

    问题 (1)CopyOnWriteArraySet是用Map实现的吗? (2)CopyOnWriteArraySet是有序的吗? (3)CopyOnWriteArraySet是并发安全的吗? (4)C ...

  9. 死磕 java集合之LinkedHashSet源码分析

    问题 (1)LinkedHashSet的底层使用什么存储元素? (2)LinkedHashSet与HashSet有什么不同? (3)LinkedHashSet是有序的吗? (4)LinkedHashS ...

随机推荐

  1. vscode 编辑器常用快捷键

    最近,打算换个编辑器,而 vscode 是一个不错的选择.大部分快捷键和 sublime 还是很像的,但有些也不一样.特此整理一份小笔记. 参考: vscode: Visual Studio Code ...

  2. 国外青少年最喜爱的聊天 app,竟然是 Google Docs

    简评: 这还真不是标题党,Google Docs 的协作中内置了实时聊天的功能,也可以进行 comments,颇有种现代「传纸条」的既视感.其实国内的石墨文档.腾讯文档等协作工具也有类似的效果,本文很 ...

  3. Java程序员必须掌握的常用Linux命令。

    Java程序员也是半个运维了,在日常开发中经常会接触到Linux环境操作.小公司的开发人员甚至是兼了全运维的工作,下面整理了一些常用的Linux操作命令. Linux常用指令 ls 显示文件或目录 - ...

  4. docker 安全

    由于容器运行在主机上,且与主机共用一套内核,因此在容器的安全使用上会涉及到容器本身以及主机的安全加固,如针对系统调用,系统资源,远程访问等都需要进行安全方面的考量. docker官网给出了简单的一些建 ...

  5. Spring Cloud Ribbon——客户端负载均衡

    一.负载均衡负载均衡(Load Balance): 建立在现有网络结构之上,它提供了一种廉价有效透明的方法扩展网络设备和服务器的带宽.增加吞吐量.加强网络数据处理能力.提高网络的灵活性和可用性.其意思 ...

  6. Java并发编程笔记之ConcurrentHashMap原理探究

    在多线程环境下,使用HashMap进行put操作时存在丢失数据的情况,为了避免这种bug的隐患,强烈建议使用ConcurrentHashMap代替HashMap. HashTable是一个线程安全的类 ...

  7. Jmeter JDBC Request 查询语句中有汉字查询结果为空的解决方法

    搜索接口我会校验返回值,查询JDBC Request 查询语句有中文字的时候查询会有问题. 解决方法很简单,在JDBC Connection Configuration的Database URL里加一 ...

  8. RocketMQ的broker启动失败解决

    RocketMQ的broker用如下命令启动: nohup sh bin/mqbroker -n localhost:9876 & 使用jps查看,系统非常卡顿,broker的名字也未显示.使 ...

  9. [TJOI 2018]智力竞赛

    Description 题库链接 给出一张 \(m\) 个点的有向图.问可重最小路径覆盖是否 \(\leq n+1\) .若不,求最多用 \(n+1\) 条路径去覆盖,最大化未覆盖点点权最小值. \( ...

  10. 数据库MongoDB

    一.MongoDB简介 MongoDB是由c++语言编写的,是一个基于分布式文件存储的开源数据库系统,在高负载的情况下,添加更多的节点,可以保证服务器性能.MongoDB旨在为web应用提供扩展的高性 ...