本文不打算延续前几篇的风格(对全部的源码加入注释),由于要理解透TreeMap的全部源码,对博主来讲,确实须要耗费大量的时间和经历,目前看来不大可能有这么多时间的投入,故这里意在经过于阅读源码对TreeMap有个宏观上的把握,并就其中一些方法的实现作比较深刻的分析。java
TreeMap是基于红黑树实现的,这里只对红黑树作个简单的介绍,红黑树是一种特殊的二叉排序树,关于二叉排序树,参见:http://blog.csdn.net/ns_code/article/details/19823463,红黑树经过一些限制,使其不会出现二叉树排序树中极端的一边倒的状况,相对二叉排序树而言,这天然提升了查询的效率。算法
二叉排序树的基本性质以下:数据结构
一、每一个节点都只能是红色或者黑色app
二、根节点是黑色函数
三、每一个叶节点(NIL节点,空节点)是黑色的。性能
四、若是一个结点是红的,则它两个子节点都是黑的。也就是说在一条路径上不能出现相邻的两个红色结点。ui
五、从任一节点到其每一个叶子的全部路径都包含相同数目的黑色节点。this
正是这些性质的限制,使得红黑树中任一节点到其子孙叶子节点的最长路径不会长于最短路径的2倍,所以它是一种接近平衡的二叉树。spa
说到红黑树,天然难免要和AVL树对比一番。相比较而言,AVL树是严格的平衡二叉树,而红黑树不算严格意义上的平衡二叉树,只是接近平衡,不会让树的高度如BST极端状况那样等于节点的个数。.net
其实能用到红黑树的地方,也均可以用AVL树来实现,但红黑树的应用却很是普遍,而AVL树则不多被使用。在执行插入、删除操做时,AVL树须要调整的次数通常要比红黑树多(红黑树的旋转调整最多只需三次),效率相对较低,且红黑树的统计性能较AVL树要好,固然AVL树在查询效率上可能更胜一筹,但实际上也高不了多少。
红黑树的插入删除操做很简单,就是单纯的二叉排序树的插入删除操做。红黑树被认为比较变态的地方天然在于插入删除后对红黑树的调整操做(旋转和着色),主要是状况分的不少,限于篇幅及博主的熟悉程度优先,这里不打算详细介绍插入删除后调整红黑树的各类状况及其实现,咱们有个宏观上的了解便可,如须详细了解,参见算法导论或一些相关的资料。
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个构造方法。
一、无参构造方法
public TreeMap() {
comparator = null;
}
采用无参构造方法,不指定比较器,这时候,排序的实现要依赖key.compareTo()方法,所以key必须实现Comparable接口,并覆写其中的compareTo方法。
二、带有比较器的构造方法
public TreeMap(Comparator<? super K> comparator) {
this.comparator = comparator;
}
采用带比较器的构造方法,这时候,排序依赖该比较器,key能够不用实现Comparable接口。
三、带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中的元素是无序存放的,所以要一个个插入到红黑树中,使其有序存放,并知足红黑树的性质。
四、带有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那么多,咱们有个宏观上的把我和比较便可。
一、TreeMap是根据key进行排序的。
它的排序和定位须要依赖比较器或覆写Comparable接口,也所以不须要key覆写hashCode方法和equals方法,就能够排除掉重复的key,而HashMap的key则须要经过覆写hashCode方法和equals方法来确保没有重复的key。
二、TreeMap的查询、插入、删除效率均没有HashMap高,通常只有要对key排序时才使用TreeMap。
三、TreeMap的key不能为null,而HashMap的key能够为null。
▷▷▷注:
对TreeSet和HashSet的源码再也不进行剖析,两者分别是基于TreeMap和HashMap实现的,只是对应的节点中只有key,而没有value,所以对TreeMap和HashMap比较了解的话,对TreeSet和HashSet的理解就会很是容易。