转:【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树在查询效率上可能更胜一筹,但实际上也高不了多少。
红黑树的插入删除操作很简单,就是单纯的二叉排序树的插入删除操作。红黑树被认为比较变态的地方自然在于插入删除后对红黑树的调整操作(旋转和着色),主要是情况分的很多,限于篇幅及博主的熟悉程度优先,这里不打算详细介绍插入删除后调整红黑树的各种情况及其实现,我们有个宏观上的了解即可,如须详细了解,参见算法导论或一些相关的资料。
TreeMap源码剖析
存储结构
TreeMap的排序是基于对key的排序实现的,它的每一个Entry代表红黑树的一个节点,Entry的数据结构如下:
- static final class Entry<K,V> implements Map.Entry<K,V> {
- // 键
- K key;
- // 值
- V value;
- // 左孩子
- Entry<K,V> left = null;
- // 右孩子
- Entry<K,V> right = null;
- // 父节点
- 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;
- }
- 。。。。。
- }
构造方法
先来看下TreeMap的构造方法。TreeMap一共有4个构造方法。
1、无参构造方法
- public TreeMap() {
- comparator = null;
- }
采用无参构造方法,不指定比较器,这时候,排序的实现要依赖key.compareTo()方法,因此key必须实现Comparable接口,并覆写其中的compareTo方法。
2、带有比较器的构造方法
- public TreeMap(Comparator<? super K> comparator) {
- this.comparator = comparator;
- }
采用带比较器的构造方法,这时候,排序依赖该比较器,key可以不用实现Comparable接口。
3、带Map的构造方法
- public TreeMap(Map<? extends K, ? extends V> m) {
- comparator = null;
- putAll(m);
- }
该构造方法同样不指定比较器,调用putAll方法将Map中的所有元素加入到TreeMap中。putAll的源码如下:
- // 将map中的全部节点添加到TreeMap中
- public void putAll(Map<? extends K, ? extends V> map) {
- // 获取map的大小
- int mapSize = map.size();
- // 如果TreeMap的大小是0,且map的大小不是0,且map是已排序的“key-value对”
- if (size==0 && mapSize!=0 && map instanceof SortedMap) {
- Comparator c = ((SortedMap)map).comparator();
- // 如果TreeMap和map的比较器相等;
- // 则将map的元素全部拷贝到TreeMap中,然后返回!
- if (c == comparator || (c != null && c.equals(comparator))) {
- ++modCount;
- try {
- buildFromSorted(mapSize, map.entrySet().iterator(),
- null, null);
- } catch (java.io.IOException cannotHappen) {
- } catch (ClassNotFoundException cannotHappen) {
- }
- return;
- }
- }
- // 调用AbstractMap中的putAll();
- // AbstractMap中的putAll()又会调用到TreeMap的put()
- super.putAll(map);
- }
显然,如果Map里的元素是排好序的,就调用buildFromSorted方法来拷贝Map中的元素,这在下一个构造方法中会重点提及,而如果Map中的元素不是排好序的,就调用AbstractMap的putAll(map)方法,该方法源码如下:
- public void putAll(Map<? extends K, ? extends V> m) {
- for (Map.Entry<? extends K, ? extends V> e : m.entrySet())
- put(e.getKey(), e.getValue());
- }
很明显它是将Map中的元素一个个put(插入)到TreeMap中的,主要因为Map中的元素是无序存放的,因此要一个个插入到红黑树中,使其有序存放,并满足红黑树的性质。
4、带有SortedMap的构造方法
- 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) {
- }
- }
首先将比较器指定为m的比较器,这取决于生成m时调用构造方法是否传入了指定的构造器,而后调用buildFromSorted方法,将SortedMap中的元素插入到TreeMap中,由于SortedMap中的元素师有序的,实际上它是根据SortedMap创建的TreeMap,将SortedMap中对应的元素添加到TreeMap中。
插入删除
插入操作即对应TreeMap的put方法,put操作实际上只需按照二叉排序树的插入步骤来操作即可,插入到指定位置后,再做调整,使其保持红黑树的特性。put源码的实现:
- public V put(K key, V value) {
- Entry<K,V> t = root;
- // 若红黑树为空,则插入根节点
- if (t == null) {
- // TBD:
- // 5045147: (coll) Adding null to an empty TreeSet should
- // throw NullPointerException
- //
- // compare(key, key); // type check
- root = new Entry<K,V>(key, value, null);
- size = 1;
- modCount++;
- return null;
- }
- int cmp;
- Entry<K,V> parent;
- // split comparator and comparable paths
- Comparator<? super K> cpr = comparator;
- // 找出(key, value)在二叉排序树中的插入位置。
- // 红黑树是以key来进行排序的,所以这里以key来进行查找。
- if (cpr != null) {
- do {
- parent = t;
- cmp = cpr.compare(key, t.key);
- if (cmp < 0)
- t = t.left;
- else if (cmp > 0)
- t = t.right;
- else
- return t.setValue(value);
- } 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);
- }
- // 为(key-value)新建节点
- 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++;
- modCount++;
- return null;
- }
这里的fixAfterInsertion便是节点插入后对树进行调整的方法,这里不做介绍。
删除操作及对应TreeMap的deleteEntry方法,deleteEntry方法同样也只需按照二叉排序树的操作步骤实现即可,删除指定节点后,再对树进行调整即可。deleteEntry方法的实现源码如下:
- // 删除“红黑树的节点p”
- 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;
- }
- }
- }
后面的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的理解就会非常容易。
转:【Java集合源码剖析】TreeMap源码剖析的更多相关文章
- java集合树状结构及源码
java集合树状结构及源码 最近一直想看一下java集合的源码,毕竟平时用的比较多,但总是感觉是跟着习惯new出来一个对象,比如ArrayList,HashMap等等,所以就简单的看了一下,了解了一下 ...
- Java 集合系列12之 TreeMap详细介绍(源码解析)和使用示例
概要 这一章,我们对TreeMap进行学习.我们先对TreeMap有个整体认识,然后再学习它的源码,最后再通过实例来学会使用TreeMap.内容包括:第1部分 TreeMap介绍第2部分 TreeMa ...
- 【转】Java 集合系列12之 TreeMap详细介绍(源码解析)和使用示例
概要 这一章,我们对TreeMap进行学习.我们先对TreeMap有个整体认识,然后再学习它的源码,最后再通过实例来学会使用TreeMap.内容包括:第1部分 TreeMap介绍第2部分 TreeMa ...
- Java集合详解及List源码分析
对于数组我们应该很熟悉,一个数组在内存中总是一块连续的存储空间,数组的创建使用new关键字,数组是引用类型的数据,一旦第一个元素的位置确定,那么后面的元素位置也就确定了,数组有一个最大的局限就是数组一 ...
- Java 集合系列Stack详细介绍(源码解析)和使用示例
Stack简介 Stack是栈.它的特性是:先进后出(FILO, First In Last Out). java工具包中的Stack是继承于Vector(矢量队列)的,由于Vector是通过数组实现 ...
- 1.Java集合-HashMap实现原理及源码分析
哈希表(Hash Table)也叫散列表,是一种非常重要的数据结构,应用场景及其丰富,许多缓存技术(比如memcached)的核心其实就是在内存中维护一张大的哈希表,而HashMap的实现原理也常常 ...
- Java集合框架之接口Collection源码分析
本文我们主要学习Java集合框架的根接口Collection,通过本文我们可以进一步了解Collection的属性及提供的方法.在介绍Collection接口之前我们不得不先学习一下Iterable, ...
- 【Java集合】试读ArrayList源码
ArrayList简介 ArrayList 是一个数组队列,相当于 动态数组.与Java中的数组相比,它的容量能动态增长.它继承于AbstractList,实现了List, RandomAccess, ...
- java集合【13】——— Stack源码分析走一波
前言 集合源码分析系列:Java集合源码分析 前面已经把Vector,ArrayList,LinkedList分析完了,本来是想开始Map这一块,但是看了下面这个接口设计框架图:整个接口框架关系如下( ...
- java集合系列——Map之TreeMap介绍(九)
一.TreeMap的简介 TreeMap是一个有序的key-value集合,基于红黑树(Red-Black tree)的 NavigableMap实现.该映射根据其键的自然顺序进行排序,或者根据创建映 ...
随机推荐
- EXT中导出表格中的数据到Excel
{ itemId: 'excel', text: '导出', iconCls: 'btnExportExcel', disabled: false, handler: function () { // ...
- swift UIButton边框添加阴影效果
btn.layer.shadowOpacity = 0.8 //阴影区域透明度 btn.layer.shadowColor = UIColor.black.cgColor // 阴影区域颜色 btn. ...
- POJ-1861-NETWORK 解题报告
Network Time Limit: 1000MS Memory Limit: 30000K Total Submissions: 16628 Accepted: 6597 Specia ...
- 如何开发自己的搜索帝国之安装ik分词器
Elasticsearch默认提供的分词器,会把每个汉字分开,而不是我们想要的根据关键词来分词,我是中国人 不能简单的分成一个个字,我们更希望 “中国人”,“中国”,“我”这样的分词,这样我们就需要 ...
- python学习===如何理解python中的return
首先要了解,函数是什么?书上可能会说函数是完成功能的模块之类的话.其实说白了,函数就是个你招来的工人.你给他一些材料,告诉他怎么用这些材料拼装,然后他负责把拼装好的成品交给你.材料就是函数的参数,成品 ...
- shell变量的替换,命令的替换,转义字符
1,shell变量的替换 变量可以根据变量是否为空或者被删除,而被替换为特定的值 ${var} 变量本来的值 $(var:-word) 如果变量为空,或者已被删除那么返回word,但是不改变va ...
- 专业辟谣----ThinkSNS不仅仅是微博程序!
[摘要]ThinkSNS(简称TS),一款全平台综合性社交系统,为国内外大中小企业和创业者提供社会化软件研发及技术解决方案,目前最新版本为ThinkSNS+. ThinkSNS这个词,第一眼应该想到的 ...
- JQuery中的回调对象
JQuery中的回调对象 回调对象(Callbacks object)模块是JQuery中的一个很基础的模块,很多其他的模块(比如Deferred.Ajax等)都依赖于Callbacks模块的实现.本 ...
- android studio多渠道多包名多apk打包
转自 利用 Android Studio 和 Gradle 打包多版本APK 搬砖的道路上,经常会有各种不同的需求,比如今天就碰到过一个打包版本的apk的要求, 比如一个apk给多个客户使用,如张三 ...
- Json-Server模拟数据接口开发
前端开发过程中调用后台接口是常事,在前后台定义好接口规范和参数后,后台接口的进度有时会成为我们的掣肘,这里推荐使用一款简单的小工具json-server,小而轻便. 确保本地已有node环境. 一:安 ...