转载请注明出处:http://blog.csdn.net/ns_code/article/details/36421085


前言

本文不打算延续前几篇的风格(对全部的源代码加入凝视),由于要理解透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的理解就会非常easy。

【Java集合源代码剖析】TreeMap源代码剖析的更多相关文章

  1. Java集合系列之TreeMap源代码分析

    一.概述 TreeMap是基于红黑树实现的.因为TreeMap实现了java.util.sortMap接口,集合中的映射关系是具有一定顺序的,该映射依据其键的自然顺序进行排序或者依据创建映射时提供的C ...

  2. 【转】Java集合:HashMap源码剖析

    Java集合:HashMap源码剖析   一.HashMap概述二.HashMap的数据结构三.HashMap源码分析     1.关键属性     2.构造方法     3.存储数据     4.调 ...

  3. Java 集合系列 12 TreeMap

    java 集合系列目录: Java 集合系列 01 总体框架 Java 集合系列 02 Collection架构 Java 集合系列 03 ArrayList详细介绍(源码解析)和使用示例 Java ...

  4. Java集合框架之TreeMap浅析

    Java集合框架之TreeMap浅析 一.TreeMap综述: TreeMap在Map中的结构如下:

  5. 【源代码】TreeMap源代码剖析

    注:下面源代码基于jdk1.7.0_11 之前介绍了一系列Map集合中的详细实现类,包含HashMap,HashTable,LinkedHashMap.这三个类都是基于哈希表实现的,今天我们介绍还有一 ...

  6. Java集合:HashMap源码剖析

    一.HashMap概述 HashMap基于哈希表的 Map 接口的实现.此实现提供所有可选的映射操作,并允许使用 null 值和 null 键.(除了不同步和允许使用 null 之外,HashMap  ...

  7. Java 集合框架 ArrayList 源码剖析

    总体介绍 ArrayList实现了List接口,是顺序容器,即元素存放的数据与放进去的顺序相同,允许放入null元素,底层通过数组实现.除该类未实现同步外,其余跟Vector大致相同.每个ArrayL ...

  8. Java 集合系列 09 HashMap详细介绍(源码解析)和使用示例

    java 集合系列目录: Java 集合系列 01 总体框架 Java 集合系列 02 Collection架构 Java 集合系列 03 ArrayList详细介绍(源码解析)和使用示例 Java ...

  9. Java 集合系列 10 Hashtable详细介绍(源码解析)和使用示例

    java 集合系列目录: Java 集合系列 01 总体框架 Java 集合系列 02 Collection架构 Java 集合系列 03 ArrayList详细介绍(源码解析)和使用示例 Java ...

  10. Java 集合系列 17 TreeSet

    java 集合系列目录: Java 集合系列 01 总体框架 Java 集合系列 02 Collection架构 Java 集合系列 03 ArrayList详细介绍(源码解析)和使用示例 Java ...

随机推荐

  1. LaTex:图片排版

    一般支持三种格式的图片排版 %% if you use PostScript figures in your article %% use the graphics package for simpl ...

  2. SQL Server 获取某时间点后修改的函数Function 并以文本格式显示

    修改查询分析器如下选项 右键=>查询选项 =>结果=>文本=> 取消 在结果集中包括列标题 的勾选 右键=>将结果保存到=> 选择 以文本格式显示结果 执行如下SQ ...

  3. 玩转Google开源C++单元测试框架Google Test系列(gtest)(转)

    转自:http://www.cnblogs.com/coderzh/archive/2009/04/06/1426755.html 前段时间学习和了解了下Google的开源C++单元测试框架Googl ...

  4. delphi Align属性

    ---------------------------------------------- -

  5. SparkSQL之更改表结构

    本文篇幅较短,内容源于自己在使用SparkSQL时碰到的一个小问题,因为在之后的数据处理过程中多次使用,所以为了加深印象,在此单独成文,以便回顾. 场景 在使用SparkSQL进行数据处理时,碰到这样 ...

  6. sudo 添加

    sudo 添加 /etc/sudoers 找到root  ALL=(ALL)  ALL那一行, 复制之后,修改用户名:

  7. Linux 倒引号、单引号、双引号

    1.倒引号表示命令,用于命令替换,获取命令的返回结果. echo now is `date` 或者 echo now is $(date) 2.单引号 name=Andy  没有问题, 如果想 nam ...

  8. CSS学习(九)-CSS背景

    一.理论: 1.background-break  a.bounding-box 背景图像在整个内联元素中进行平铺 b.each-box 背景图像在行内中进行平铺 c.continuous 下一行的背 ...

  9. Web.xml 错误或异常页面配置

         <error-page>          <error-code>404</error-code>          <location>/ ...

  10. JMeter 四:建立高级web测试计划

    发送带有Header的请求 参考:http://jmeter.apache.org/usermanual/build-adv-web-test-plan.html#header_manager Jme ...