当前位置: 首页 > news >正文

【Java集合源代码剖析】TreeMap源代码剖析

前言

    本文不打算延续前几篇的风格(对全部的源代码增加凝视)。由于要理解透TreeMap的全部源代码。对博主来说,确实须要耗费大量的时间和经历。眼下看来不大可能有这么多时间的投入,故这里意在通过于阅读源代码对TreeMap有个宏观上的把握。并就当中一些方法的实现做比較深入的分析。

红黑树简单介绍

    TreeMap是基于红黑树实现的,这里仅仅对红黑树做个简单的介绍。红黑树是一种特殊的二叉排序树。关于二叉排序树。參见:http://blog.csdn.net/ns_code/article/details/19823463,红黑树通过一些限制,使其不会出现二叉树排序树中极端的一边倒的情况,相对二叉排序树而言。这自然提高了查询的效率。

    二叉排序树的基本性质例如以下:

    1、每一个节点都仅仅能是红色或者黑色

    2、根节点是黑色

    3、每一个叶节点(NIL节点。空节点)是黑色的。

    4、假设一个结点是红的,则它两个子节点都是黑的。也就是说在一条路径上不能出现相邻的两个红色结点。

    5、从任一节点到其每一个叶子的全部路径都包括同样数目的黑色节点。

    正是这些性质的限制。使得红黑树中任一节点到其子孙叶子节点的最长路径不会长于最短路径的2倍,因此它是一种接近平衡的二叉树。

    说到红黑树。自然不免要和AVL树对照一番。

相比較而言,AVL树是严格的平衡二叉树。而红黑树不算严格意义上的平衡二叉树。仅仅是接近平衡。不会让树的高度如BST极端情况那样等于节点的个数。事实上能用到红黑树的地方。也都能够用AVL树来实现,但红黑树的应用却非常广泛,而AVL树则非常少被使用。在运行插入、删除操作时。AVL树须要调整的次数一般要比红黑树多(红黑树的旋转调整最多仅仅需三次),效率相对较低。且红黑树的统计性能较AVL树要好,当然AVL树在查询效率上可能更胜一筹,但实际上也高不了多少。

    红黑树的插入删除操作非常easy,就是单纯的二叉排序树的插入删除操作。

红黑树被觉得比較变态的地方自然在于插入删除后对红黑树的调整操作(旋转和着色)。主要是情况分的非常多。限于篇幅及博主的熟悉程度优先。这里不打算具体介绍插入删除后调整红黑树的各种情况及事实上现,我们有个宏观上的了解就可以。如须具体了解。參见算法导论或一些相关的资料。

TreeMap源代码剖析

    存储结构

    TreeMap的排序是基于对key的排序实现的,它的每个Entry代表红黑树的一个节点。Entry的数据结构例如以下:

[java]  view plain copy
  1. static final class Entry<K,V> implements Map.Entry<K,V> {    
  2.      // 键    
  3.      K key;    
  4.      // 值    
  5.      V value;    
  6.      // 左孩子    
  7.      Entry<K,V> left = null;    
  8.      // 右孩子    
  9.      Entry<K,V> right = null;    
  10.      // 父节点    
  11.      Entry<K,V> parent;    
  12.      // 当前节点颜色    
  13.      boolean color = BLACK;    
  14.   
  15.      // 构造函数    
  16.      Entry(K key, V value, Entry<K,V> parent) {    
  17.          this.key = key;    
  18.          this.value = value;    
  19.          this.parent = parent;    
  20.      }    
  21.   
  22. 。。。

      

  23. }  

    构造方法

    先来看下TreeMap的构造方法。

TreeMap一共同拥有4个构造方法。

    1、无參构造方法

[java]  view plain copy
  1. public TreeMap() {    
  2.     comparator = null;    
  3. }    
    採用无參构造方法。不指定比較器,这时候,排序的实现要依赖key.compareTo()方法。因此key必须实现Comparable接口,并覆写当中的compareTo方法。

    2、带有比較器的构造方法

[java]  view plain copy
  1. public TreeMap(Comparator<?

     

    super K> comparator) {    
  2.     this.comparator = comparator;    
  3. }    
    採用带比較器的构造方法,这时候,排序依赖该比較器,key能够不用实现Comparable接口。

    3、带Map的构造方法

[java]  view plain copy
  1. public TreeMap(Map<? extends K, ?

     

    extends V> m) {    
  2.     comparator = null;    
  3.     putAll(m);    
  4. }    
    该构造方法相同不指定比較器,调用putAll方法将Map中的全部元素增加到TreeMap中。

putAll的源代码例如以下:

[java]  view plain copy
  1. // 将map中的所有节点加入到TreeMap中    
  2. public void putAll(Map<?

     

    extends K, ?

     

    extends V> map) {    
  3.     // 获取map的大小    
  4.     int mapSize = map.size();    
  5.     // 假设TreeMap的大小是0,且map的大小不是0,且map是已排序的“key-value对”    
  6.     if (size==0 && mapSize!=0 && map instanceof SortedMap) {    
  7.         Comparator c = ((SortedMap)map).comparator();    
  8.         // 假设TreeMap和map的比較器相等。    
  9.         // 则将map的元素所有复制到TreeMap中。然后返回!    
  10.         if (c == comparator || (c != null && c.equals(comparator))) {    
  11.             ++modCount;    
  12.             try {    
  13.                 buildFromSorted(mapSize, map.entrySet().iterator(),    
  14.                             nullnull);    
  15.             } catch (java.io.IOException cannotHappen) {    
  16.             } catch (ClassNotFoundException cannotHappen) {    
  17.             }    
  18.             return;    
  19.         }    
  20.     }    
  21.     // 调用AbstractMap中的putAll();    
  22.     // AbstractMap中的putAll()又会调用到TreeMap的put()    
  23.     super.putAll(map);    
  24. }   
    显然,假设Map里的元素是排好序的。就调用buildFromSorted方法来拷贝Map中的元素。这在下一个构造方法中会重点提及,而假设Map中的元素不是排好序的,就调用AbstractMap的putAll(map)方法,该方法源代码例如以下:

[java]  view plain copy
  1. public void putAll(Map<? extends K, ?

     

    extends V> m) {    
  2.     for (Map.Entry<? extends K, ?

     

    extends V> e : m.entrySet())    
  3.         put(e.getKey(), e.getValue());    
  4. }   
    非常明显它是将Map中的元素一个个put(插入)到TreeMap中的。主要由于Map中的元素是无序存放的,因此要一个个插入到红黑树中。使其有序存放,并满足红黑树的性质。

    4、带有SortedMap的构造方法

[java]  view plain copy
  1. public TreeMap(SortedMap<K, ? extends V> m) {    
  2.     comparator = m.comparator();    
  3.     try {    
  4.         buildFromSorted(m.size(), m.entrySet().iterator(), nullnull);    
  5.     } catch (java.io.IOException cannotHappen) {    
  6.     } catch (ClassNotFoundException cannotHappen) {    
  7.     }    
  8. }    
    首先将比較器指定为m的比較器,这取决于生成m时调用构造方法是否传入了指定的构造器。而后调用buildFromSorted方法,将SortedMap中的元素插入到TreeMap中,因为SortedMap中的元素师有序的,实际上它是依据SortedMap创建的TreeMap。将SortedMap中相应的元素加入到TreeMap中。

    插入删除

    插入操作即相应TreeMap的put方法,put操作实际上仅仅需依照二叉排序树的插入步骤来操作就可以,插入到指定位置后,再做调整,使其保持红黑树的特性。put源代码的实现:

[java]  view plain copy
  1. public V put(K key, V value) {    
  2.     Entry<K,V> t = root;    
  3.     // 若红黑树为空,则插入根节点    
  4.     if (t == null) {    
  5.     // TBD:    
  6.     // 5045147: (coll) Adding null to an empty TreeSet should    
  7.     // throw NullPointerException    
  8.     //    
  9.     // compare(key, key); // type check    
  10.         root = new Entry<K,V>(key, value, null);    
  11.         size = 1;    
  12.         modCount++;    
  13.         return null;    
  14.     }    
  15.     int cmp;    
  16.     Entry<K,V> parent;    
  17.     // split comparator and comparable paths    
  18.     Comparator<? super K> cpr = comparator;    
  19.     // 找出(key, value)在二叉排序树中的插入位置。

      

      
  20.     // 红黑树是以key来进行排序的。所以这里以key来进行查找。    
  21.     if (cpr != null) {    
  22.         do {    
  23.             parent = t;    
  24.             cmp = cpr.compare(key, t.key);    
  25.             if (cmp < 0)    
  26.                 t = t.left;    
  27.             else if (cmp > 0)    
  28.                 t = t.right;    
  29.             else   
  30.                 return t.setValue(value);    
  31.         } while (t != null);    
  32.     }    
  33.     else {    
  34.         if (key == null)    
  35.             throw new NullPointerException();    
  36.         Comparable<?

     super K> k = (Comparable<? super K>) key;    

  37.         do {    
  38.             parent = t;    
  39.             cmp = k.compareTo(t.key);    
  40.             if (cmp < 0)    
  41.                 t = t.left;    
  42.             else if (cmp > 0)    
  43.                 t = t.right;    
  44.             else   
  45.                 return t.setValue(value);    
  46.         } while (t != null);    
  47.     }    
  48.     // 为(key-value)新建节点    
  49.     Entry<K,V> e = new Entry<K,V>(key, value, parent);    
  50.     if (cmp < 0)    
  51.         parent.left = e;    
  52.     else   
  53.         parent.right = e;    
  54.     // 插入新的节点后,调用fixAfterInsertion调整红黑树。    
  55.     fixAfterInsertion(e);    
  56.     size++;    
  57.     modCount++;    
  58.     return null;    
  59. }    
    这里的fixAfterInsertion便是节点插入后对树进行调整的方法,这里不做介绍。


    删除操作及相应TreeMap的deleteEntry方法。deleteEntry方法相同也仅仅需依照二叉排序树的操作步骤实现就可以,删除指定节点后,再对树进行调整就可以。deleteEntry方法的实现源代码例如以下:

[java]  view plain copy
  1. // 删除“红黑树的节点p”    
  2. private void deleteEntry(Entry<K,V> p) {    
  3.     modCount++;    
  4.     size--;    
  5.   
  6.     if (p.left != null && p.right != null) {    
  7.         Entry<K,V> s = successor (p);    
  8.         p.key = s.key;    
  9.         p.value = s.value;    
  10.         p = s;    
  11.     }   
  12.   
  13.     Entry<K,V> replacement = (p.left != null ?

     p.left : p.right);    

  14.   
  15.     if (replacement != null) {    
  16.         replacement.parent = p.parent;    
  17.         if (p.parent == null)    
  18.             root = replacement;    
  19.         else if (p == p.parent.left)    
  20.             p.parent.left  = replacement;    
  21.         else   
  22.             p.parent.right = replacement;    
  23.   
  24.         p.left = p.right = p.parent = null;    
  25.   
  26.         if (p.color == BLACK)    
  27.             fixAfterDeletion(replacement);    
  28.     } else if (p.parent == null) {   
  29.         root = null;    
  30.     } else {  
  31.         if (p.color == BLACK)    
  32.             fixAfterDeletion(p);    
  33.   
  34.         if (p.parent != null) {    
  35.             if (p == p.parent.left)    
  36.                 p.parent.left = null;    
  37.             else if (p == p.parent.right)    
  38.                 p.parent.right = null;    
  39.             p.parent = null;    
  40.         }    
  41.     }    
  42. }    
    后面的fixAfterDeletion方法便是节点删除后对树进行调整的方法,这里不做介绍。

    其它非常多方法这里不再一一介绍。

几点总结

    本文对TreeMap的分析较前几篇文章有些浅尝辄止,TreeMap用的没有HashMap那么多,我们有个宏观上的把我和比較就可以。

    1、TreeMap是依据key进行排序的,它的排序和定位须要依赖比較器或覆写Comparable接口,也因此不须要key覆写hashCode方法和equals方法。就能够排除掉反复的key,而HashMap的key则须要通过覆写hashCode方法和equals方法来确保没有反复的key。

    2、TreeMap的查询、插入、删除效率均没有HashMap高,一般仅仅有要对key排序时才使用TreeMap。

    3、TreeMap的key不能为null,而HashMap的key能够为null。


    注:对TreeSet和HashSet的源代码不再进行剖析,二者各自是基于TreeMap和HashMap实现的。仅仅是相应的节点中仅仅有key。而没有value,因此对TreeMap和HashMap比較了解的话,对TreeSet和HashSet的理解就会很easy。

相关文章:

  • Transient修饰符的使用
  • 【算法】 算法和数据结构绪论
  • 【转】Servlet 生命周期、工作原理
  • Openssl源代码整理学习---含P7/P10/P12说明
  • LeetCode OJ 之 Ugly Number II (丑数-二)
  • [计算机术语]缺省
  • (一)Thymeleaf用法——Thymeleaf简介
  • 【Python】 命名空间与LEGB规则
  • 通用的进程监控脚本process_monitor.sh使用方法
  • Spark on Yarn集群搭建详细过程
  • MySQL学习笔记-数据类型与操作数据表
  • sklearn包学习
  • 转一个简单的vue.js的图片懒加载的插件代码!
  • 学渣的逆袭:他叛逆狂妄,却搞出不少大新闻
  • MySQL 数据库分表分区
  • 《Java编程思想》读书笔记-对象导论
  • 【402天】跃迁之路——程序员高效学习方法论探索系列(实验阶段159-2018.03.14)...
  • bootstrap创建登录注册页面
  • canvas 绘制双线技巧
  • CSS3 变换
  • overflow: hidden IE7无效
  • v-if和v-for连用出现的问题
  • 理解在java “”i=i++;”所发生的事情
  • 那些被忽略的 JavaScript 数组方法细节
  • 前端知识点整理(待续)
  • 一些关于Rust在2019年的思考
  • ​​​​​​​​​​​​​​汽车网络信息安全分析方法论
  • ​3ds Max插件CG MAGIC图形板块为您提升线条效率!
  • ​LeetCode解法汇总2182. 构造限制重复的字符串
  • #etcd#安装时出错
  • (02)vite环境变量配置
  • (a /b)*c的值
  • (Spark3.2.0)Spark SQL 初探: 使用大数据分析2000万KF数据
  • (二)基于wpr_simulation 的Ros机器人运动控制,gazebo仿真
  • (六)c52学习之旅-独立按键
  • (心得)获取一个数二进制序列中所有的偶数位和奇数位, 分别输出二进制序列。
  • (转) Android中ViewStub组件使用
  • 、写入Shellcode到注册表上线
  • .NET “底层”异步编程模式——异步编程模型(Asynchronous Programming Model,APM)...
  • .net 8 发布了,试下微软最近强推的MAUI
  • .NET CORE 2.0发布后没有 VIEWS视图页面文件
  • .NET Framework 的 bug?try-catch-when 中如果 when 语句抛出异常,程序将彻底崩溃
  • .NET MVC 验证码
  • .NET/C# 推荐一个我设计的缓存类型(适合缓存反射等耗性能的操作,附用法)
  • .NET/C# 中你可以在代码中写多个 Main 函数,然后按需要随时切换
  • .NET6使用MiniExcel根据数据源横向导出头部标题及数据
  • .sdf和.msp文件读取
  • @RunWith注解作用
  • @基于大模型的旅游路线推荐方案
  • [ 2222 ]http://e.eqxiu.com/s/wJMf15Ku
  • [ 隧道技术 ] cpolar 工具详解之将内网端口映射到公网
  • [2023-年度总结]凡是过往,皆为序章
  • [AX]AX2012 SSRS报表Drill through action
  • [emacs] CUA的矩形块操作很给力啊
  • [HarmonyOS]第一课:从简单的页面开始