Java集合——TreeMap源码详解
0. 前言
本文对TreeMap的源码进行了解析,本文原创,转载请注明出处:http://blog.csdn.net/seu_calvin/article/details/52746000
先对TreeMap的特性进行一个概述:
(1)TreeMap 是一个有序的key-value集合,它是通过红黑树实现的。因为红黑树是平衡的二叉搜索树,所以其put(包含update操作)、get、remove的时间复杂度都为log(n)。
(2)TreeMap 相比于HashMap多实现了了NavigableMap接口(也就是这个接口,决定了TreeMap与HashMap的不同:HashMap的key是无序的,TreeMap的key是有序的)。
- public class TreeMap<K,V>
- extends AbstractMap<K,V>
- implements NavigableMap<K,V>, Cloneable, java.io.Serializable
(3)TreeMap是非同步的。
1. 红黑树
红黑树是一种二叉搜索树,让我们在一起回忆下二叉搜索树的一些性质:
二叉搜索树左子树的值小于根节点,右子树的值大于根节点。很明显,二叉搜索树每进行一次判断就是能将问题的规模减少一半,所以二叉搜索树查找元素的时间复杂度为log(n)。下面在看一下红黑树的样子:
叶子节点为上图中的NIL节点,国内一些教材中没有这个NIL节点,我们在画图时有时也会省略这些NIL节点,但是我们需要明确,当我们说叶子节点时,指的就是这些NIL节点。
红黑树通过下面5条规则,保证了树是平衡的:
(1)树的节点只有红与黑两种颜色。
(2)根节点为黑色的。
(3)叶子节点为黑色的。
(4)红色节点的子节点必定是黑色的。
(5)从任意节点出发,到其后继的叶子节点的路径中,黑色节点的数目相同。
满足了上面5个条件后,就能够保证根节点到叶子节点的最长路径不会大于根节点到叶子最短路径的2倍。
简单证明如下:假设根节点到叶子节点最短的路径中,黑色节点数目为B,那么根据性质5,根节点到叶子节点的最长路径中,黑色节点数目也是B,最长的情况就是每两个黑色节点中间有个红色节点(也就是红黑相间的情况),所以红色节点最多为B-1个。所以B+B-1<=2B得证。
红黑树操作包括插入、删除、左旋、右旋,这里有个可视化的红黑树操作网站,把这些操作都按动画做出来了,生动形象。
2. NavigableMap接口
- public interface NavigableMap<K,V> extends SortedMap<K,V>
- public interface SortedMap<K,V> extends Map<K,V>
TreeMap实现了NavigableMap接口,NavigableMap是JDK1.6新增的,NavigableMap继承了SortedMap(这个Map是有序的),顺序一般是指由Comparable接口提供的keys的自然序,或者也可以在创建SortedMap实例时,指定一个Comparator来决定。插入SortedMap中的key的类都必须继承Comparable类(或指定一个comparator),这样才能通过k1.compareTo(k2)或comparator.compare(k1, k2)来比较两个key。
NavigableMap接口在SortedMap的基础上增加了一些导航方法来返回与搜索目标最近的元素。例如下面这些方法:
- lowerEntry//返回所有比给定Map.Entry小的元素
- floorEntry//返回所有比给定Map.Entry小或相等的元素
- ceilingEntry//返回所有比给定Map.Entry大或相等的元素
- higherEntry//返回所有比给定Map.Entry大的元素
3. TreeMap的存储结构
Entry作为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;
- }
- ......
- }
4. TreeMap的构造方法
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);
- }
- // putAll()将m中的所有元素添加到TreeMap中
- public void putAll(Map<? extends K, ? extends V> m) {
- for (Map.Entry<? extends K, ? extends V> e : m.entrySet())
- put(e.getKey(), e.getValue());
- }
- //4.构造一个与指定有序映射具有相同映射关系和相同排序顺序的新的树映射
- public TreeMap(SortedMap<K, ? extends V> m){
- comparator = m.comparator();
- try{
- buildFromSorted(m.size(), m.entrySet().iterator(), null, null);}
- catch (Exception e){}
- }
4. TreeMap的put操作
- public V put(K key, V value) {
- Entry<K,V> t = root;
- //若根节点为空,则以(key,value)为参数新建节点
- if (t == null){
- compare(key, key); // type check
- root = new Entry<>(key, value, null);//第三个参数为父节点
- size = 1;
- modCount++;
- return null;
- }
- int cmp;
- Entry<K,V> parent;
- 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); //直到把新节点要插入的位置置空
- }
- //如果没有定义比较器,则采用默认的排序算法进行创建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(e);
- size++;
- modCount++;
- return null;
- }
从源码中可以总结出几个需要注意的点:
(1)TreeMap的查询和更新都涉及比较操作,故TreeMap的键必须实现Comparable接口或者构造时传入比较器(比较器优先级更高)。
(2)默认的排序算法中put操作不允许null键,但是值(value)允许为null;若传入自定义比较器,可以手动处理null键的情况。
(3)键重复的情况下,新值会覆盖掉旧值。
6. TreeMap的get操作
- 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();
- Comparable<? super K> k = (Comparable<? super K>) key;
- Entry<K,V> p = root;
- while (p != null) {//根据Comparable接口定义的比较规则查找
- int cmp = k.compareTo(p.key);
- if (cmp < 0)//待查结点在左子树
- p = p.left;
- else if (cmp > 0)//待查结点在右子树
- p = p.right;
- else
- return p;
- }
- return null;//没找到
- }
- final Entry<K,V> getEntryUsingComparator(Object key) {//根据比较器定义的比较规则查找
- 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;
- }
7. TreeMap的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;
- }
8. TreeMap的两种迭代操作
- //1.根据entrySet()和Iterator迭代器遍历
- Integer integ = null;
- Iterator iter = map.entrySet().iterator();
- while(iter.hasNext()) {
- Map.Entry entry = (Map.Entry)iter.next();
- key = (String)entry.getKey();
- integ = (Integer)entry.getValue();
- }
- //2.根据keySet()和Iterator迭代器遍历
- String key = null;
- Integer integ = null;
- Iterator iter = map.keySet().iterator();
- while (iter.hasNext()) {
- key = (String)iter.next();
- integ = (Integer)map.get(key);
- }
9. TreeMap和HashMap的关系
9.1 TreeMap和HashMap的相同点
(1)两者均是线程不安全的。
(2)两者插入节点时,key重复均覆盖旧值。
9.2 TreeMap和HashMap的不同点
(1)HashMap的实现基于数组和单链表,而TreeMap的实现基于红黑树,必要时数组会扩容、保持树平衡。
(2)HashMap的key是无序的,TreeMap的key是有序的。
(3)TreeMap要求key必须实现Comparable接口,或者初始化时传入Comparator比较器。
(4)HashMap增删改查操作的时间复杂度为O(1),TreeMap增删改查操作的时间复杂度为O(log(n))。
(5)HashMap允许null值,TreeMap中默认的排序算法中put操作不允许null键,但是值(value)允许为null;若传入自定义比较器,可以手动处理null键的情况。
10. TreeMap和TreeSet的关系
TreeMap 是 Map 接口的常用实现类,而TreeSet 是 Set 接口的常用实现类。虽然 TreeMap 和TreeSet 实现的接口规范不同,但 TreeSet 底层是通过 TreeMap 来实现的(如同HashSet底层是是通过HashMap来实现、LinkedHashSet底层基于LinkedHashMap实现一样),因此二者的实现方式完全一样,都是红黑树算法。
相同点:
(1)TreeMap和TreeSet都是有序的集合(仅仅key对象有序)。
(2)TreeMap和TreeSet都是非同步集合,不过都可以使用Collections.synchroinzedMap()来实现同步。
(3)内部对元素的操作时间复杂度都是O(logN),而HashMap/HashSet/HashTable则为O(1)。
不同点:
最主要的区别就是TreeSet和TreeMap分别实现Set和Map接口,相同的道理可以用于HashSet与HashMap、LinkedHashMap与LinkedHashSet。
TreeSet只存储一个对象(通过Map的Key的部分实现,下面贴出的HashSet的构造方法源码可以证明),而TreeMap存储两个对象Key和Value,前者通过put()将元素放入Map,后者使用add()将元素放入Set。HashSet内部是大量借用HashMap的实现,它本身不过是调用HashMap的一个代理。
- private transient HashMap<E,Object> map;
- // Dummy value to associate with an Object in the backing Map
- private static final Object PRESENT = new Object();
- /**
- * Constructs a new, empty set; the backing HashMap instance has
- * default initial capacity (16) and load factor (0.75).
- */
- public HashSet() {
- map = new HashMap<E,Object>();
- }
- public boolean add(E e) {
- return map.put(e, PRESENT)==null;
- }
- public boolean remove(Object o) {
- return map.remove(o)==PRESENT;
- }
- public boolean contains(Object o) {
- return map.containsKey(o);
- }
转载请注明出处:http://blog.csdn.net/seu_calvin/article/details/52746000
同时请多点赞多多支持~
Java集合——TreeMap源码详解的更多相关文章
- Java集合——ArrayList源码详解
) ArrayList 实现了RandomAccess, Cloneable, java.io.Serializable三个标记接口,表示它自身支持快速随机访问,克隆,序列化. public clas ...
- Java集合——LinkedList源码详解
)LinkedList直接继承于AbstractSequentialList,同时实现了List接口,也实现了Deque接口. AbstractSequentialList为顺序访问的数据存储结构提供 ...
- Java集合——LinkedHashMap源码详解
个KV.LinkedHashMap不仅像HashMap那样对其进行基于哈希表和单链表的Entry数组+ next链表的存储方式,而且还结合了LinkedList的优点,为每个Entry节点增加了前驱和 ...
- java 基础数据结构源码详解及数据结构算法
http://www.cnblogs.com/skywang12345/category/455711.html http://www.cnblogs.com/liqiu/p/3302607.html
- 数据结构与算法系列2 线性表 使用java实现动态数组+ArrayList源码详解
数据结构与算法系列2 线性表 使用java实现动态数组+ArrayList源码详解 对数组有不了解的可以先看看我的另一篇文章,那篇文章对数组有很多详细的解析,而本篇文章则着重讲动态数组,另一篇文章链接 ...
- 【java集合框架源码剖析系列】java源码剖析之TreeMap
注:博主java集合框架源码剖析系列的源码全部基于JDK1.8.0版本.本博客将从源码角度带领大家学习关于TreeMap的知识. 一TreeMap的定义: public class TreeMap&l ...
- Java源码详解系列(十)--全面分析mybatis的使用、源码和代码生成器(总计5篇博客)
简介 Mybatis 是一个持久层框架,它对 JDBC 进行了高级封装,使我们的代码中不会出现任何的 JDBC 代码,另外,它还通过 xml 或注解的方式将 sql 从 DAO/Repository ...
- Activiti架构分析及源码详解
目录 Activiti架构分析及源码详解 引言 一.Activiti设计解析-架构&领域模型 1.1 架构 1.2 领域模型 二.Activiti设计解析-PVM执行树 2.1 核心理念 2. ...
- 源码详解系列(七) ------ 全面讲解logback的使用和源码
什么是logback logback 用于日志记录,可以将日志输出到控制台.文件.数据库和邮件等,相比其它所有的日志系统,logback 更快并且更小,包含了许多独特并且有用的特性. logback ...
随机推荐
- 3.Servlet实例
一.基础实例 1.参照如下例子创建maven web工程: https://www.cnblogs.com/lukelook/p/9187313.html 2.创建一个简单的Servlet 类 pac ...
- SQL SERVER Management Studio编写SQL时没有智能提示的解决方式
1. 检查设置里是否启用智能感知(Intellisence),可以在“工具”→“选项”里设置 2. 如果启用后还是无效,可以新建一个查询窗口查询,输入关键词的前面几个字母看是否有提示(或者使用Ctrl ...
- Azure Document DB Repository 的实现
阅读 需要大约 5 分钟. 前景: Azure Cosmos DB 由 Microsoft 提供,是全球分布式多模型数据库. 通过 Azure Cosmos DB 跨任意数量的 Azure 地理区域 ...
- Codewars, Leetcode, Hackerrank. Online Judges Reviews
http://jasonjl.me/blog/2015/03/30/practical-programming-practice-services/ Codewars, Leetcode, Hacke ...
- 基于php-fpm的配置详解
php5.3自带php-fpm/usr/local/php/etc/php-fpm.confpid = run/php-fpm.pidpid设置,默认在安装目录中的var/run/php-fpm.pi ...
- html端输入数据,利用qrcode.js生成打印二维码
在前端页面中导入qrcode.js(下载)和jquery.js(下载) index.html <script> function print() { var textbox1 = $('i ...
- (转)Matlab增加块注释
1)方法一选中你要加注释的内容,然后选择工具菜单“text|comment”就可以了,如果要把注释变为语句,同样选中要转变的语句,然后用鼠标选择“text|uncomment”就可以了.用键盘的快捷键 ...
- 列表 ->join---> 字符串 转类型:x--->y类型 y(x)
列表 ->join---> 字符串 转类型:x--->y类型 y(x)
- 使用Oracle的instr函数与索引配合提高模糊查询的效率
使用Oracle的instr函数与索引配合提高模糊查询的效率 一般来说,在Oracle数据库中,我们对tb表的name字段进行模糊查询会采用下面两种方式:1.select * from tb wher ...
- jQuery实现简易轮播图的效果
(图片素材取自于小米官网) 刚开始接触jQuery的学习,个人觉得如果为了实现多数的动态效果,jQuery的确很简易方便. 下面简易的轮播图效果,还请前辈多多指教~ (努力学习react vue an ...