转:【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的数据结构如下:

[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. Entry(K key, V value, Entry<K,V> parent) {
  16. this.key = key;
  17. this.value = value;
  18. this.parent = parent;
  19. }
  20. 。。。。。
  21. }

构造方法

先来看下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. null, null);
  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(), null, null);
  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. if (p.left != null && p.right != null) {
  6. Entry<K,V> s = successor (p);
  7. p.key = s.key;
  8. p.value = s.value;
  9. p = s;
  10. }
  11. Entry<K,V> replacement = (p.left != null ? p.left : p.right);
  12. if (replacement != null) {
  13. replacement.parent = p.parent;
  14. if (p.parent == null)
  15. root = replacement;
  16. else if (p == p.parent.left)
  17. p.parent.left  = replacement;
  18. else
  19. p.parent.right = replacement;
  20. p.left = p.right = p.parent = null;
  21. if (p.color == BLACK)
  22. fixAfterDeletion(replacement);
  23. } else if (p.parent == null) {
  24. root = null;
  25. } else {
  26. if (p.color == BLACK)
  27. fixAfterDeletion(p);
  28. if (p.parent != null) {
  29. if (p == p.parent.left)
  30. p.parent.left = null;
  31. else if (p == p.parent.right)
  32. p.parent.right = null;
  33. p.parent = null;
  34. }
  35. }
  36. }

后面的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的理解就会非常容易。

时间: 2025-01-05 00:11:29

转:【Java集合源码剖析】TreeMap源码剖析的相关文章

【转】Java 集合系列12之 TreeMap详细介绍(源码解析)和使用示例

概要 这一章,我们对TreeMap进行学习.我们先对TreeMap有个整体认识,然后再学习它的源码,最后再通过实例来学会使用TreeMap.内容包括:第1部分 TreeMap介绍第2部分 TreeMap数据结构第3部分 TreeMap源码解析(基于JDK1.6.0_45)第4部分 TreeMap遍历方式第5部分 TreeMap示例 转载请注明出处:http://www.cnblogs.com/skywang12345/admin/EditPosts.aspx?postid=3310928 第1部

Java 集合系列12之 TreeMap详细介绍(源码解析)和使用示例

概要 这一章,我们对TreeMap进行学习.我们先对TreeMap有个整体认识,然后再学习它的源码,最后再通过实例来学会使用TreeMap.内容包括:第1部分 TreeMap介绍第2部分 TreeMap数据结构第3部分 TreeMap源码解析(基于JDK1.6.0_45)第4部分 TreeMap遍历方式第5部分 TreeMap示例 转载:http://www.cnblogs.com/skywang12345/admin/EditPosts.aspx?postid=3310928 第1部分 Tre

1.Java集合-HashMap实现原理及源码分析

哈希表(Hash  Table)也叫散列表,是一种非常重要的数据结构,应用场景及其丰富,许多缓存技术(比如memcached)的核心其实就是在内存中维护一张大的哈希表,而HashMap的实现原理也常常出现在各类的面试题中,这里对java集合框架中的对应实现HashMap的实现原理进行讲解,然后对JDK7的HashMap的源码进行分析 哈希算法,是一类算法: 哈希表(Hash  Table)是一种数据结构: 哈希函数:是支撑哈希表的一类函数: HashMap 是 Java中用哈希数据结构实现的Ma

【源码】TreeMap源码剖析

注:以下源码基于jdk1.7.0_11 之前介绍了一系列Map集合中的具体实现类,包括HashMap,HashTable,LinkedHashMap.这三个类都是基于哈希表实现的,今天我们介绍另一种Map集合,TreeMap.TreeMap是基于红黑树实现的. 介绍TreeMap之前,回顾下红黑树的性质: 首先,我们要明确,红黑树是一种二叉排序树,而且是平衡二叉树.因而红黑树具有排序树的所有特点,任意结点的左子树(如果有的话)的值比该结点小,右子树(如果有的话)的值比该结点大.二叉排序树各项操作

6.Java集合-LinkedList实现原理及源码分析

Java中LinkedList的部分源码(本文针对1.7的源码) LinkedList的基本结构 jdk1.7之后,node节点取代了 entry ,带来的变化是,将1.6中的环形结构优化为了直线型链表结构,从双向循环链表变成了双向链表 在LinkedList中,我们把链子的"环"叫做"节点",每个节点都是同样的结构.节点与节点之间相连,构成了我们LinkedList的基本数据结构,也是LinkedList的核心. 我们再来看一下LinkedList在jdk1.6和

【Java集合】试读LinkedList源码

LinkedList的本质是双向链表.(01) LinkedList继承于AbstractSequentialList,并且实现了Dequeue接口. (02) LinkedList包含两个重要的成员:header 和 size. header是双向链表的表头,它是双向链表节点所对应的类Entry的实例.Entry中包含成员变量: previous, next, element.其中,previous是该节点的上一个节点,next是该节点的下一个节点,element是该节点所包含的值.  siz

Java集合框架之一:ArrayList源码分析

版权声明:本文为博主原创文章,转载请注明出处,欢迎交流学习! ArrayList底层维护的是一个动态数组,每个ArrayList实例都有一个容量.该容量是指用来存储列表元素的数组的大小.它总是至少等于列表的大小.随着向 ArrayList 中不断添加元素,其容量也自动增长. ArrayList不是同步的(也就是说不是线程安全的),如果多个线程同时访问一个ArrayList实例,而其中至少一个线程从结构上修改了列表,那么它必须保持外部同步,在多线程环境下,可以使用Collections.synch

深入理解JAVA集合系列四:ArrayList源码解读

在开始本章内容之前,这里先简单介绍下List的相关内容. List的简单介绍 有序的collection,用户可以对列表中每个元素的插入位置进行精确的控制.用户可以根据元素的整数索引(在列表中的位置)访问元素,并搜索列表中的元素.列表通常允许重复的元素,且允许null元素的存放. ArrayList的简单介绍 JDK中这样定义ArrayList:List接口的大小可变数据的实现. 主要有以下特点: 1.有序 2.线程不安全 3.元素可以重复 4.可以存放null值 顾名思义,取名ArrayLis

2.Java集合-ConcurrentHashMap实现原理及源码分析

一.为何用ConcurrentHashMap 在并发编程中使用HashMap可能会导致死循环,而使用线程安全的HashTable效率又低下. 线程不安全的HashMap 在多线程环境下,使用HashMap进行put操作会引起死循环,导致CPU利用率接近100%,所以在并发情况下不能使用HashMap 效率低下的HashTable Hashtable使用synchronized来保证线程的安全,但是在线程竞争激烈的情况下Hashtable的效率非常低下.当一个线程访问Hashtable的同步方法,

深入理解JAVA集合系列二:ConcurrentHashMap源码解读

HashMap和Hashtable的区别 在正式开始这篇文章的主题之前,我们先来比较下HashMap和Hashtable之间的差异点: 1.Hashtable是线程安全的,它对外提供的所有方法都是都使用了synchronized,是同步的,而HashMap是非线程安全的. 2.Hashtable不允许value为空,否则会抛出空指针异常: 而HashMap中key.value都可以为空. 1 public synchronized V put(K key, V value) { 2 // Mak