注:下面源代码基于jdk1.7.0_11

之前介绍了一系列Map集合中的详细实现类,包含HashMap,HashTable,LinkedHashMap。这三个类都是基于哈希表实现的,今天我们介绍还有一种Map集合,TreeMap。TreeMap是基于红黑树实现的。
介绍TreeMap之前,回想下红黑树的性质
首先,我们要明白,红黑树是一种二叉排序树,并且是平衡二叉树。因而红黑树具有排序树的全部特点,随意结点的左子树(假设有的话)的值比该结点小,右子树(假设有的话)的值比该结点大。二叉排序树各项操作的平均时间复杂度为O(logn),可是最坏情况下,二叉排序树会退化成单链表,此时时间复杂度为O(n),红黑树在二叉排序树的基础上,对其添加了一系列约束,使得其尽可能地平衡,红黑树的查询和更新的时间复杂度为O(logn)。
红黑树的五条性质:
1.每一个结点要么是红色,要么是黑色;
2.根结点为黑色;
3.叶结点为黑色(空结点);
4.若一个结点为红色,则其子结点为黑色;
5.每一个叶结点到根结点的路径中黑色结点的数目一致(黑高度同样)。
红黑树的查询操作与二叉排序树同样,重点是其插入和删除操作。红黑树的插入和删除操作在二叉排序树的基础上添加了修复操作,由于插入和删除可能会导致树不再满足红黑树性质,这时候会通过着色、旋转操作对其进行修复。

以下来看TreeMap的实现。
类声明:
public class TreeMap<K,V>
extends AbstractMap<K,V>
implements NavigableMap<K,V>, Cloneable, java.io.Serializable
TreeMap相同继承AbstractMap,可是它实现了NavigableMap接口,而NavigableMap接口继承自SortedMap接口。
TreeMap有四个成员变量,当中root是红黑树的根结点,因为红黑树的查询和更新操作须要比較,故而有个比較器comparator,默认情况下,comparator为空,这就要求我们的键必须实现Comparable接口,以定义比較规则。
 private final Comparator<? super K> comparator;//比較器
private transient Entry<K,V> root = null;//树根
/**
* The number of entries in the tree
*/
private transient int size = 0;//大小
/**
* The number of structural modifications to the tree.
*/
private transient int modCount = 0;//改动次数

构造器:

public TreeMap() {
comparator = null;//比較器为空
} public TreeMap(Comparator<? super K> comparator) {//传入比較器
this.comparator = comparator;
} public TreeMap(Map<? extends K, ? extends V> m) {
comparator = null;
putAll(m);
} 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) {
}
}

在查看TreeMap的查询和更新操作之前,我们先看下Entry的实现,事实上我们都能够猜到,Entry既然是TreeMap存储的结点,那么其必定包含例如以下几个域:数据(键、值)、父结点、左孩子、右孩子、颜色。事实正是如此:

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;//默认颜色
/**
* Make a new cell with given key, value, and parent, and with
* {@code null} child links, and BLACK color.
*/
Entry(K key, V value, Entry<K,V> parent) {
this.key = key;
this.value = value;
this.parent = parent;
}
public K getKey() {
return key;
}
public V getValue() {
return value;
}
public V setValue(V value) {
V oldValue = this.value;
this.value = value;
return oldValue;
}
public boolean equals(Object o) {
if (!(o instanceof Map.Entry))
return false;
Map.Entry<?,?> e = (Map.Entry<?,?>)o;
return valEquals(key,e.getKey()) && valEquals(value,e.getValue());
}
public int hashCode() {
int keyHash = (key==null ? 0 : key.hashCode());
int valueHash = (value==null ? 0 : value.hashCode());
return keyHash ^ valueHash;
}
public String toString() {
return key + "=" + value;
}
}

以下来看put方法:

public V put(K key, V value) {//向红黑树中插入键值对
Entry<K,V> t = root;
if (t == null) {//假设树为空
compare(key, key); // type (and possibly null) check
root = new Entry<>(key, value, null);
size = 1;
modCount++;
return null;
}
int cmp;
Entry<K,V> parent;//父结点
// split comparator and comparable paths
Comparator<? super K> cpr = comparator;
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 {//假设未定义比較器,那么key必须实现Comparable接口
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);
}
//找到插入点之后,创建新结点,插入之。
Entry<K,V> e = new Entry<>(key, value, parent);
if (cmp < 0)//推断是挂到左边还是右边
parent.left = e;
else
parent.right = e;
fixAfterInsertion(e);//进行着色和旋转等操作修复红黑树
size++;
modCount++;
return null;
}
明白下面几点:
1.TreeMap的查询和更新操作都涉及到比較操作,故而TreeMap的键必须实现Comparable接口或者构造时得传入比較器(既实现了Comparable接口又传入了比較器情况下,比較器优先);
2.put操作不同意null键,可是值(value)同意为null;
3.键反复的情况下,新值会覆盖掉旧值。


再看get方法:
 public V get(Object key) {//查询操作
Entry<K,V> p = getEntry(key);
return (p==null ? null : p.value);
}

调用getEntry方法查询指定键值:

final Entry<K,V> getEntry(Object key) {//跟普通二叉排序树的查询操作一致
// Offload comparator-based version for sake of performance
if (comparator != null)//存在比較器
return getEntryUsingComparator(key);//依据比較器定义的比較规则查找
if (key == null)
throw new NullPointerException();
Comparable<? super K> k = (Comparable<? super K>) key;
Entry<K,V> p = root;
while (p != null) {//依据Comparable接口定义的比較规则查找
int cmp = k.compareTo(p.key);
if (cmp < 0)//待查结点在左子树
p = p.left;
else if (cmp > 0)//待查结点在右子树
p = p.right;
else
return p;
}
return null;//没找到
}
final Entry<K,V> getEntryUsingComparator(Object key) {//依据比較器定义的比較规则查找
K k = (K) key;
Comparator<? super K> cpr = comparator;
if (cpr != null) {
Entry<K,V> p = root;
while (p != null) {
int cmp = cpr.compare(k, p.key);
if (cmp < 0)
p = p.left;
else if (cmp > 0)
p = p.right;
else
return p;
}
}
return null;
}
对照HashMap近乎O(1)的查找复杂度,TreeMap显得略有不足。
再看remove删除操作:
  public V remove(Object key) {
Entry<K,V> p = getEntry(key);//首先找到待删结点
if (p == null)
return null;
V oldValue = p.value;
deleteEntry(p);//删除结点
return oldValue;
}

尽管看上去寥寥几行代码,事实上逻辑十分复杂,详细体如今删除结点后的恢复操作。

 private void deleteEntry(Entry<K,V> p) {//删除一个结点
modCount++;
size--;
// If strictly internal, copy successor's element to p and then make p
// point to successor.
if (p.left != null && p.right != null) {//p的左右孩子都存在
Entry<K,V> s = successor(p);//找到p的直接后继(顺着p右子树一直向左)
p.key = s.key;//用直接后继替代p
p.value = s.value;
p = s;
} // p has 2 children
//以下操作将释放s结点,并修复红黑树
// Start fixup at replacement node, if it exists.
Entry<K,V> replacement = (p.left != null ? p.left : p.right);
if (replacement != null) {
// Link replacement to parent
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;
// Null out links so they are OK to use by fixAfterDeletion.
p.left = p.right = p.parent = null;
// Fix replacement
if (p.color == BLACK)
fixAfterDeletion(replacement);//修复红黑树
} else if (p.parent == null) { // return if we are the only node.
root = null;
} else { // No children. Use self as phantom replacement and unlink.
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;
}
}
}
successtor函数用于找一个结点的中序后继(參见之前写的一篇怎样得到一个结点的中序后继,算法一致):
迭代器遍历操作正是基于successtor操作完毕的。所以遍历TreeMap得到的键值对是有序的。
 static <K,V> TreeMap.Entry<K,V> successor(Entry<K,V> t) {//查找中序后继
if (t == null)
return null;
else if (t.right != null) {//假设存在右子树
Entry<K,V> p = t.right;
while (p.left != null)//顺着右子树,向左搜索
p = p.left;
return p;
} else {//假设不存在右子树
Entry<K,V> p = t.parent;//顺着父亲,向上搜索
Entry<K,V> ch = t;
while (p != null && ch == p.right) {//假设当前结点是父结点的右孩子,那么继续向上
ch = p;
p = p.parent;
}
return p;
}
}

对称地,还有个查找直接前驱的函数:

 static <K,V> Entry<K,V> predecessor(Entry<K,V> t) {
if (t == null)
return null;
else if (t.left != null) {//若存在左子树
Entry<K,V> p = t.left;
while (p.right != null)//顺着左子树,向右搜索
p = p.right;
return p;
} else {
Entry<K,V> p = t.parent;
Entry<K,V> ch = t;
while (p != null && ch == p.left) {
ch = p;
p = p.parent;
}
return p;
}
}
注:文章有益忽略了更新操作中涉及到的红黑树修复动作(着色,旋转),此部分内容较为复杂,作者眼下也没有全然吃透。

总结:
1.TreeMap的实现基于红黑树;
2.TreeMap不同意插入null键,但同意null值;
3.TreeMap线程不安全;
4.插入结点时,若键反复,则新值会覆盖旧值;
5.TreeMap要求key必须实现Comparable接口,或者初始化时传入Comparator比較器;
6.遍历TreeMap得到的结果集是有序的(中序遍历);
7.TreeMap的各项操作的平均时间复杂度为O(logn).


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

  1. 【Java集合源代码剖析】TreeMap源代码剖析

    转载请注明出处:http://blog.csdn.net/ns_code/article/details/36421085 前言 本文不打算延续前几篇的风格(对全部的源代码加入凝视),由于要理解透Tr ...

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

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

  3. TreeMap源代码深入剖析

    第1部分 TreeMap介绍 A Red-Black tree based NavigableMap implementation. The map is sorted according to th ...

  4. java TreeMap 源代码分析 平衡二叉树

    TreeMap 的实现就是红黑树数据结构,也就说是一棵自平衡的排序二叉树,这样就可以保证当需要快速检索指定节点. TreeSet 和 TreeMap 的关系 为了让大家了解 TreeMap 和 Tre ...

  5. [Java] TreeMap - 源代码学习笔记

    TreeMap 实现了 SortedMap 和 NavigableMap 接口,所有本文还会记录 SortedMap 和 NavigableMap 的阅读笔记. SortedMap 1. 排序的比较应 ...

  6. TreeMap - 源代码学习笔记

    TreeMap 实现了 NavigableMap 接口,而NavigableMap 接口继承于 SortedMap接口. 所有本文还会记录 SortedMap 和 NavigableMap 的阅读笔记 ...

  7. 图像库---Image Datasets---OpenSift源代码---openSurf源代码

    1.Computer Vision Datasets on the web http://www.cvpapers.com/datasets.html 2.Dataset Reference http ...

  8. MINA2 源代码学习--源代码结构梳理

    一.mina总体框架与案例: 1.总体结构图: 简述:以上是一张来自网上比較经典的图,总体上揭示了mina的结构,当中IoService包括clientIoConnector和服务端IoAccepto ...

  9. Java To CSharp源代码转换

    前言 开发环境 客户端:Unity3D开发(C#) 服务器:Java (基于Java7) 日   期:2016年09月 需求说明 部分服务器的部分逻辑功能在客户端实现一遍,可以简单的理解为服务器的部分 ...

随机推荐

  1. JAVA泛型之<? extends T>:(通配符上限)和<? super T>(通配符下限)

    一.通配符上限和通配符下限接受的类型 通配符上限:<? extends T> 通配符下限:<? super T> 以下代码是测试结果,注释为解释说明 package xayd. ...

  2. Mysql rr和rc隔离

    REPEATABLE READ This is the default isolation level for InnoDB. For consistent reads, there is an im ...

  3. Android笔记二十七.Service组件入门(一).什么是Service?

    转载请表明出处:http://blog.csdn.net/u012637501(嵌入式_小J的天空) 一.Service 1.Service简单介绍     Service为Android四大组件之中 ...

  4. poj 3278 Catch That Cow (bfs搜索)

    Catch That Cow Time Limit: 2000MS   Memory Limit: 65536K Total Submissions: 46715   Accepted: 14673 ...

  5. svn经常使用命令具体解释(非常全,非常有用)

    ubuntu下安装subversionclient: sudo apt-getinstall subversion subversion-tools 1.检出 svn  co  http://路径(文 ...

  6. Oracle Cursor的使用

    When Oracle Database executes a SQL statement, it stores the result set and processing information i ...

  7. 知识网之C++总结

    米老师常说的一句话:构造知识网. 立即要考试了.就让我们构造一下属于C++的知识网.首先从总体上了解C++: 从图中能够了解到,主要有五部分.而当我们和之前的知识联系的话,也就剩下模板和运算符重载以及 ...

  8. 使用RouteDebugger对MVC路由进行调试

    在Asp.Net MVC程序中,路由是MVC程序的入口,每一个Http请求都要经过路由计算,然后匹配到相应的Controller和Action.通常我们的路由都会注册在Global.asax.cs文件 ...

  9. 【剑指offer】面试题24:二叉搜索树的兴许前序遍历序列

    分析: 前序: 根 左 右 后序: 左 由 根 二叉搜索树: 左 < 根 < 右 那么这就非常明显了. def ifpost(postArray, start, end): #one or ...

  10. 使用python向Redis批量导入数据

    1.使用pipeline进行批量导入数据.包含先使用rpush插入数据,然后使用expire改动过期时间 class Redis_Handler(Handler): def connect(self) ...