Java集合源代码剖析TreeMap源代码剖析
Posted cxchanpin
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java集合源代码剖析TreeMap源代码剖析相关的知识,希望对你有一定的参考价值。
前言
本文不打算延续前几篇的风格(对全部的源代码增加凝视)。由于要理解透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树在查询效率上可能更胜一筹,但实际上也高不了多少。
红黑树的插入删除操作非常easy,就是单纯的二叉排序树的插入删除操作。
红黑树被觉得比較变态的地方自然在于插入删除后对红黑树的调整操作(旋转和着色)。主要是情况分的非常多。限于篇幅及博主的熟悉程度优先。这里不打算具体介绍插入删除后调整红黑树的各种情况及事实上现,我们有个宏观上的了解就可以。如须具体了解。參见算法导论或一些相关的资料。
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;
- }
2、带有比較器的构造方法
-
public TreeMap(Comparator<?
super K> comparator) {
- this.comparator = comparator;
- }
3、带Map的构造方法
-
public TreeMap(Map<? extends K, ?
extends V> m) {
- comparator = null;
- putAll(m);
- }
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);
- }
-
public void putAll(Map<? extends K, ?
extends V> m) {
-
for (Map.Entry<? extends K, ?
extends V> e : m.entrySet())
- put(e.getKey(), e.getValue());
- }
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) {
- }
- }
插入删除
插入操作即相应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;
- }
删除操作及相应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;
- }
- }
- }
其它非常多方法这里不再一一介绍。
几点总结
本文对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源代码剖析的主要内容,如果未能解决你的问题,请参考以下文章