转载请注明出处: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. Mac DBeaver Client home is not specified for connection解决办法

    问题: 一般出现这个问题是由于DBeaver 程序无法自动找到数据库的安装目录,在进行数据的导入导出时无法执行,这是因为执行指令需要数据库相关命令的支持. 解决办法: 我用的是mysql,这里拿mys ...

  2. java常用命令行

    1.javac(编译java源文件) javac是用来编译.java文件的. 例子: package com.fjassa.domain;  public class Human.public cla ...

  3. iOS:quartz2D绘图(画一些简单的图形,如直线、三角形、圆、矩形、文字等)

    前一篇几乎已经详细介绍了Quartz2D的所有知识,这一篇以及后面就不废话了,主要是用具体的实例来演示绘图效果. 这里我们先来绘制一些简单的图形(如直线.三角形.圆.矩形.文字.图像),它有两种方式可 ...

  4. 【Docker】mesos如何修改hostport默认端口范围?

    1.marathon文档:https://mesosphere.github.io/marathon/docs/native-docker.html Static port mapping: It's ...

  5. threadlocal 变量 跟synchronized 关键字的关系

    为什么叫threadloca变量呢,经过大量的查资料发现threadlocal并不是之前理解的控制线程用的东西,它其实也属于一类变量,只不过是线程的局部变量,它的作用就是实现线程间对该变量的唯一线程调 ...

  6. iptables不小心把127.0.0.1封了,导致redis连不上

    写了个脚本扫描apache日志,自动把恶意攻击者的ip交给iptables给封掉 谁知道一不小心把127.0.0.1也给封了... 直接导致redis无法链接. redis-server服务正常启动, ...

  7. RocketMQ 拉取消息-文件获取

    看完了上一篇的<RocketMQ 拉取消息-通信模块>,请求进入PullMessageProcessor中,接着 PullMessageProcessor.processRequest(f ...

  8. 二叉树(9)----打印二叉树中第K层的第M个节点,非递归算法

    1.二叉树定义: typedef struct BTreeNodeElement_t_ { void *data; } BTreeNodeElement_t; typedef struct BTree ...

  9. java提高篇-----理解java的三大特性之封装

    在<Think in java>中有这样一句话:复用代码是Java众多引人注目的功能之一.但要想成为极具革命性的语言,仅仅能够复制代码并对加以改变是不够的,它还必须能够做更多的事情.在这句 ...

  10. iOS Mapkit 定位REGcode地理位置偏移

    在iOS上,使用系统Mapkit定位,获取到的坐标会有偏移: 今有需求,用系统Mapkit定位,并Regcode出实际地理位置,修正偏移: 解决方案: 使用MapView的代理 - (void)map ...