Java集合之TreeMap源码分析
一、概述
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,具有了针对给定搜索目标返回最接近匹配项的导航方法。其方法 lowerEntry
、floorEntry
、ceilingEntry
和 higherEntry
分别返回与小于、小于等于、大于等于、大于给定键的键关联的 Map.Entry
对象,如果不存在这样的键,则返回 null
。类似地,方法 lowerKey
、floorKey
、ceilingKey
和 higherKey
只返回关联的键。所有这些方法是为查找条目而不是遍历条目而设计的。
二、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源码分析的更多相关文章
- 死磕 java集合之TreeMap源码分析(四)-内含彩蛋
欢迎关注我的公众号"彤哥读源码",查看更多源码系列文章, 与彤哥一起畅游源码的海洋. 二叉树的遍历 我们知道二叉查找树的遍历有前序遍历.中序遍历.后序遍历. (1)前序遍历,先遍历 ...
- 死磕 java集合之TreeMap源码分析(一)- 内含红黑树分析全过程
欢迎关注我的公众号"彤哥读源码",查看更多源码系列文章, 与彤哥一起畅游源码的海洋. 简介 TreeMap使用红黑树存储元素,可以保证元素按key值的大小进行遍历. 继承体系 Tr ...
- 死磕 java集合之TreeMap源码分析(三)- 内含红黑树分析全过程
欢迎关注我的公众号"彤哥读源码",查看更多源码系列文章, 与彤哥一起畅游源码的海洋. 删除元素 删除元素本身比较简单,就是采用二叉树的删除规则. (1)如果删除的位置有两个叶子节点 ...
- 死磕 java集合之TreeMap源码分析(二)- 内含红黑树分析全过程
欢迎关注我的公众号"彤哥读源码",查看更多源码系列文章, 与彤哥一起畅游源码的海洋. 插入元素 插入元素,如果元素在树中存在,则替换value:如果元素不存在,则插入到对应的位置, ...
- 死磕 java集合之DelayQueue源码分析
问题 (1)DelayQueue是阻塞队列吗? (2)DelayQueue的实现方式? (3)DelayQueue主要用于什么场景? 简介 DelayQueue是java并发包下的延时阻塞队列,常用于 ...
- 死磕 java集合之PriorityBlockingQueue源码分析
问题 (1)PriorityBlockingQueue的实现方式? (2)PriorityBlockingQueue是否需要扩容? (3)PriorityBlockingQueue是怎么控制并发安全的 ...
- 死磕 java集合之PriorityQueue源码分析
问题 (1)什么是优先级队列? (2)怎么实现一个优先级队列? (3)PriorityQueue是线程安全的吗? (4)PriorityQueue就有序的吗? 简介 优先级队列,是0个或多个元素的集合 ...
- 死磕 java集合之CopyOnWriteArraySet源码分析——内含巧妙设计
问题 (1)CopyOnWriteArraySet是用Map实现的吗? (2)CopyOnWriteArraySet是有序的吗? (3)CopyOnWriteArraySet是并发安全的吗? (4)C ...
- 死磕 java集合之LinkedHashSet源码分析
问题 (1)LinkedHashSet的底层使用什么存储元素? (2)LinkedHashSet与HashSet有什么不同? (3)LinkedHashSet是有序的吗? (4)LinkedHashS ...
随机推荐
- ELK实战(Springboot日志输出查找)
需求 把分布式系统,集群日志集中处理快速查询 搭建ELK并与springboot日志输出结合 搭建ELK 基于我前面的elasticsearch搭建博客文档docker-compose.yml基础上进 ...
- 自动化测试之数据库操作pymysql
1.下载并导入pymysql 2.配置参数连接mysql db = pymysql.connect(**config) config = { 'host': str(host), 主机地址 'user ...
- JavaScript的Date类的函数特殊处理导致的问题
记得以前参加校招的时候,总是有日期相关的面试题,比如计算两个日期之间的间隔天数.以前还觉得这种题就是为了纯粹为了面试的,但工作了之后,还就碰到了跟日期相关的bug.下面是一段js代码,是要把字符串描述 ...
- [视频]K8飞刀 Google黑客功能教程
[视频]K8飞刀 Google黑客功能教程 链接:https://pan.baidu.com/s/1kbK5jNH8ZaddUEeQ9IwTaw 提取码:lwl6
- 课程四(Convolutional Neural Networks),第四 周(Special applications: Face recognition & Neural style transfer) —— 2.Programming assignments:Art generation with Neural Style Transfer
Deep Learning & Art: Neural Style Transfer Welcome to the second assignment of this week. In thi ...
- 高手速成android开源项目【View篇】
主要介绍那些不错个性化的View,包括ListView.ActionBar.Menu.ViewPager.Gallery.GridView.ImageView.ProgressBar及其他如Dialo ...
- Git 强制回退到某个历史版本再推送到远程
1. 使用 git log 命令历史版本记录回退版本 git reset --hard f6a7c803a6931a9eca011d4e097389e0845cbe49 2. 推送到远程 git pu ...
- C#容器类,性能介绍
http://www.php.cn/csharp-article-354819.html 1 indexer []声明的变量必须是固定长度的,即长度是静态的:object[] objectArray ...
- Apache Commons Beanutils 一 (使用PropertyUtils访问Bean属性)
BeanUtils简要描述 beanutils,顾名思义,是java bean的一个工具类,可以帮助我们方便的读取(get)和设置(set)bean属性值.动态定义和访问bean属性: 细心的话,会发 ...
- 从零开始学 Web 之 ES6(一)ES5严格模式
大家好,这里是「 从零开始学 Web 系列教程 」,并在下列地址同步更新...... github:https://github.com/Daotin/Web 微信公众号:Web前端之巅 博客园:ht ...