【Java集合源代码剖析】TreeMap源代码剖析
转载请注明出处: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源代码剖析的更多相关文章
- Java集合系列之TreeMap源代码分析
一.概述 TreeMap是基于红黑树实现的.因为TreeMap实现了java.util.sortMap接口,集合中的映射关系是具有一定顺序的,该映射依据其键的自然顺序进行排序或者依据创建映射时提供的C ...
- 【转】Java集合:HashMap源码剖析
Java集合:HashMap源码剖析 一.HashMap概述二.HashMap的数据结构三.HashMap源码分析 1.关键属性 2.构造方法 3.存储数据 4.调 ...
- Java 集合系列 12 TreeMap
java 集合系列目录: Java 集合系列 01 总体框架 Java 集合系列 02 Collection架构 Java 集合系列 03 ArrayList详细介绍(源码解析)和使用示例 Java ...
- Java集合框架之TreeMap浅析
Java集合框架之TreeMap浅析 一.TreeMap综述: TreeMap在Map中的结构如下:
- 【源代码】TreeMap源代码剖析
注:下面源代码基于jdk1.7.0_11 之前介绍了一系列Map集合中的详细实现类,包含HashMap,HashTable,LinkedHashMap.这三个类都是基于哈希表实现的,今天我们介绍还有一 ...
- Java集合:HashMap源码剖析
一.HashMap概述 HashMap基于哈希表的 Map 接口的实现.此实现提供所有可选的映射操作,并允许使用 null 值和 null 键.(除了不同步和允许使用 null 之外,HashMap ...
- Java 集合框架 ArrayList 源码剖析
总体介绍 ArrayList实现了List接口,是顺序容器,即元素存放的数据与放进去的顺序相同,允许放入null元素,底层通过数组实现.除该类未实现同步外,其余跟Vector大致相同.每个ArrayL ...
- Java 集合系列 09 HashMap详细介绍(源码解析)和使用示例
java 集合系列目录: Java 集合系列 01 总体框架 Java 集合系列 02 Collection架构 Java 集合系列 03 ArrayList详细介绍(源码解析)和使用示例 Java ...
- Java 集合系列 10 Hashtable详细介绍(源码解析)和使用示例
java 集合系列目录: Java 集合系列 01 总体框架 Java 集合系列 02 Collection架构 Java 集合系列 03 ArrayList详细介绍(源码解析)和使用示例 Java ...
- Java 集合系列 17 TreeSet
java 集合系列目录: Java 集合系列 01 总体框架 Java 集合系列 02 Collection架构 Java 集合系列 03 ArrayList详细介绍(源码解析)和使用示例 Java ...
随机推荐
- java浏览器控件jxbrowser(简单demo模拟自动登录与点击)
写在前面: 老大让我写个脚本自动给他写dms有一段时间了,说实话当时不知道老大指的这个脚本是什么?毕竟是做web的,难道是写个数据库sql语句脚本吗?也就放在了一边.巧了,最近一个朋友说他之前写了个程 ...
- bzoj 1636: [Usaco2007 Jan]Balanced Lineup -- 线段树
1636: [Usaco2007 Jan]Balanced Lineup Time Limit: 5 Sec Memory Limit: 64 MBSubmit: 772 Solved: 560线 ...
- 20172333 2017-2018-2 《Java程序设计》第4周学习总结
20172333 2017-2018-2 <Java程序设计>第4周学习总结 教材学习内容 1.类结构的定义与概念 2.利用实例数据建立对象状态的概念 3.描述可见性修饰符作用在方法和数据 ...
- Migrating Oracle on UNIX to SQL Server on Windows
Appendices Published: April 27, 2005 On This Page Appendix A: SQL Server for Oracle Professionals Ap ...
- java多线程加锁是对谁加锁?
1.java多线程加锁是对谁加锁? 答:当然是对共享资源加锁啊,对谁进行访问修改,就对象进行加锁.以便使多线程按序访问此共享对象 比如: 在具体的Java代码中需要完成一下两个操作:把竞争访问的资源类 ...
- as3垃圾回收机制
as3垃圾回收机制 垃圾回收机制详解 能力越大责任越大,这对actionscript3.0来说一点没错.引入这些新控件带来一个副作用:垃圾收集器不再支持自动为你收集 垃圾等假设.也就是说Flash开发 ...
- 从Cell的视图推出一个新的界面
先写一个方法, 强制增加一个navigation的属性. 这样self就可以调出来navigation了 - (UINavigationController*)naviController { for ...
- 动态jdk启动项目
昨天遇到,服务器安装jdk1.7,但是springboot项目用jdk1.8编译的,所以需要指定jdk版本启动: nohup /root/jdk1.8.0_11/bin/java -jar /root ...
- Foreach控件使用
在SSIS中,Foreach控件可以循环处理选中的文件中的文件,例如sql文件,Excel文件等.下面就介绍一个比较经典的案例,将一个文件夹中包含的多个Excel文件循环导入至数据库中. 第一步:在数 ...
- [转]SQLSERVER存储过程调用不同数据库的数据_存储过程中通过链接服务器访问远程服务器
本文转自:http://blog.csdn.net/nnaabbcc/article/details/7967761 存储过程调用不同数据库的数据 在存储过程调用不同数据库的数据该如何做,比如在存储过 ...