总体介绍 Java TreeMap 实现了SortedMap 接口,也就是说会按照 key 的大小顺序对 Map 中的元素进行排序,key 大小的评判可以通过其本身的自然顺序(natural ordering),也可以通过构造时传入的比较器(Comparator)。
TreeMap 底层通过红黑树(Red-Black tree)实现containsKey() , get() , put() , remove() 都有着 log(n) 的时间复杂度。
1 2 3 4 5 6 7 private  final  Comparator<? super  K> comparator;private  transient  Entry<K,V> root;private  transient  int  size = 0 ;private  transient  int  modCount = 0 ;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 static  final  class  Entry <K ,V > implements  Map .Entry <K ,V >     K key;     V value;     Entry<K,V> left;     Entry<K,V> right;     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 是非同步的(not synchronized),如果需要在多线程环境使用,需要程序员手动同步;或者通过如下方式将TreeMap 包装成(wrapped)同步的:
SortedMap m = Collections.synchronizedSortedMap(new TreeMap(...));
红黑树是一种近似平衡的二叉查找树,它能够确保任何一个节点的左右子树的高度差不会超过二者中较低那个的一倍。 
具体来说,红黑树是满足如下条件的二叉查找树(binary search tree):
每个节点要么是红色,要么是黑色。 
根节点必须是黑色 
红色节点不能连续(也即是,红色节点的孩子和父亲都不能是红色)。 
对于每个节点,从该点至 null(树尾端)的任何路径,都含有相同个数的黑色节点。 
 
在树的结构发生改变时(插入或者删除操作),往往会破坏上述条件3或条件4,需要通过调整使得查找树重新满足红黑树的条件。
预备知识 前文说到当查找树的结构发生改变时,红黑树的条件可能被破坏,需要通过调整使得查找树重新满足红黑树的条件。调整可以分为两类:一类是颜色调整,即改变某个节点的颜色;另一类是结构调整,即改变查找树的结构关系。结构调整过程包含两个基本操作:左旋(Rotate Left) ,右旋(RotateRight) 。
左旋 左旋的过程是将 x 的右子树绕 x 逆时针旋转,使得 x 的右子树成为 x 的父亲,同时修改相关节点的引用。旋转之后,二叉查找树的属性仍然满足。
TreeMap 中左旋代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 private  void  rotateLeft (Entry<K,V> p)      if  (p != null ) {         Entry<K,V> r = p.right;         p.right = r.left;         if  (r.left != null )             r.left.parent = p;         r.parent = p.parent;         if  (p.parent == null )             root = r;         else  if  (p.parent.left == p)             p.parent.left = r;         else              p.parent.right = r;         r.left = p;         p.parent = r;     } } 
右旋 右旋的过程是将x的左子树绕x顺时针旋转,使得x的左子树成为x的父亲,同时修改相关节点的引用。旋转之后,二叉查找树的属性仍然满足。
TreeMap 中右旋代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 private  void  rotateRight (Entry<K,V> p)      if  (p != null ) {         Entry<K,V> l = p.left;         p.left = l.right;         if  (l.right != null ) l.right.parent = p;         l.parent = p.parent;         if  (p.parent == null )             root = l;         else  if  (p.parent.right == p)             p.parent.right = l;         else  p.parent.left = l;         l.right = p;         p.parent = l;     } } 
寻找节点后继 对于一棵二叉查找树,给定节点t,其后继(树中比大于t的最小的那个元素)可以通过如下方式找到:
t 的右子树不空,则t的后继是其右子树中最小的那个元素。 
t 的右孩子为空,则t的后继是其第一个向左走的祖先。 
 
后继节点在红黑树的删除操作中将会用到。
TreeMap 中寻找节点后继的代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 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;     } } 
方法剖析 get() get(Object key) 方法根据指定的 key 值返回对应的 value ,该方法调用了 getEntry(Object key) 得到相应的 entry ,然后返回 entry.value 。因此 getEntry() 是算法的核心。算法思想是根据 key 的自然顺序(或者比较器顺序)对二叉查找树进行查找,直到找到满足 k.compareTo(p.key) == 0 的 entry。
具体代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 final  Entry<K,V> getEntry (Object key)      ......     if  (key == null )         throw  new  NullPointerException();     Comparable<? super  K> k = (Comparable<? super  K>) key;     Entry<K,V> p = root;     while  (p != null ) {         int  cmp = k.compareTo(p.key);         if  (cmp < 0 )             p = p.left;         else  if  (cmp > 0 )             p = p.right;         else              return  p;     }     return  null ; } 
put() put(K key, V value) 方法是将指定的 key ,  value 对添加到 map 里。该方法首先会对 map 做一次查找,看是否包含该元组,如果已经包含则直接返回,查找过程类似于 getEntry() 方法;如果没有找到则会在红黑树中插入新的 entry ,如果插入之后破坏了红黑树的约束,还需要进行调整(旋转,改变某些节点的颜色)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 public  V put (K key, V value)  	......     int  cmp;     Entry<K,V> parent;     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++;     return  null ; } 
上述代码的插入部分并不难理解:首先在红黑树上找到合适的位置,然后创建新的 entry 并插入(当然,新插入的节点一定是树的叶子)。难点是调整函数 fixAfterInsertion() ,前面已经说过,调整往往需要1.改变某些节点的颜色,2.对某些节点进行旋转。
调整函数 fixAfterInsertion() 的具体代码如下,其中用到了上文中提到的 rotateLeft() 和 rotateRight() 函数。通过代码我们能够看到,情况2其实是落在情况3内的。情况4~情况6跟前三种情况是对称的,因此图解中并没有画出后三种情况,读者可以参考代码自行理解。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 private  void  fixAfterInsertion (Entry<K,V> x)      x.color = RED;     while  (x != null  && x != root && x.parent.color == RED) {         if  (parentOf(x) == leftOf(parentOf(parentOf(x)))) {             Entry<K,V> y = rightOf(parentOf(parentOf(x)));             if  (colorOf(y) == RED) {                 setColor(parentOf(x), BLACK);                               setColor(y, BLACK);                                         setColor(parentOf(parentOf(x)), RED);                       x = parentOf(parentOf(x));                              } else  {                 if  (x == rightOf(parentOf(x))) {                     x = parentOf(x);                                            rotateLeft(x);                                          }                 setColor(parentOf(x), BLACK);                               setColor(parentOf(parentOf(x)), RED);                       rotateRight(parentOf(parentOf(x)));                     }         } else  {             Entry<K,V> y = leftOf(parentOf(parentOf(x)));             if  (colorOf(y) == RED) {                 setColor(parentOf(x), BLACK);                               setColor(y, BLACK);                                         setColor(parentOf(parentOf(x)), RED);                       x = parentOf(parentOf(x));                              } else  {                 if  (x == leftOf(parentOf(x))) {                     x = parentOf(x);                                            rotateRight(x);                                         }                 setColor(parentOf(x), BLACK);                               setColor(parentOf(parentOf(x)), RED);                       rotateLeft(parentOf(parentOf(x)));                      }         }     }     root.color = BLACK; } 
remove() remove(Object key) 的作用是删除 key 值对应的 entry ,该方法首先通过上文中提到的 getEntry(Object key) 方法找到 key 值对应的 entry ,然后调用 deleteEntry(Entry<K,V> entry) 删除对应的 entry 。由于删除操作会改变红黑树的结构,有可能破坏红黑树的约束,因此有可能要进行调整。
getEntry() 函数前面已经讲解过,这里重点放 deleteEntry() 上,该函数删除指定的 entry 并在红黑树的约束被破坏时进行调用 fixAfterDeletion(Entry<K,V> x) 进行调整。
由于红黑树是一棵增强版的二叉查找树,红黑树的删除操作跟普通二叉查找树的删除操作也就非常相似,唯一的区别是红黑树在节点删除之后可能需要进行调整。 现在考虑一棵普通二叉查找树的删除过程,可以简单分为两种情况:
删除点p的左右子树都为空,或者只有一棵子树非空。 
删除点p的左右子树都非空。 
 
对于上述情况1,处理起来比较简单,直接将p删除(左右子树都为空时),或者用非空子树替代p(只有一棵子树非空时);对于情况2,可以用p的后继s(树中大于p的最小的那个元素)代替p,然后使用情况1删除s(此时s一定满足情况1,可以画画看)。
基于以上逻辑,红黑树的节点删除函数 deleteEntry() 代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 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() 。首先请思考一下,删除了哪些点才会导致调整?只有删除点是BLACK的时候,才会触发调整函数 ,因为删除RED节点不会破坏红黑树的任何约束,而删除BLACK节点会破坏规则4。
跟上文中讲过的 fixAfterInsertion() 函数一样,这里也要分成若干种情况。记住,无论有多少情况,具体的调整操作只有两种:1.改变某些节点的颜色,2.对某些节点进行旋转。
上述图解的总体思想是:将情况1首先转换成情况2,或者转换成情况3和情况4。当然,该图解并不意味着调整过程一定是从情况1开始。通过后续代码我们还会发现几个有趣的规则:a).如果是由情况1之后紧接着进入的情况2,那么情况2之后一定会退出循环(因为x为红色);b).一旦进入情况3和情况4,一定会退出循环(因为x为root)。
删除后调整函数 fixAfterDeletion() 的具体代码如下,其中用到了上文中提到的 rotateLeft() 和 rotateRight() 函数。通过代码我们能够看到,情况3其实是落在情况4内的。情况5~情况8跟前四种情况是对称的,因此图解中并没有画出后四种情况,读者可以参考代码自行理解。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 private  void  fixAfterDeletion (Entry<K,V> x)      while  (x != root && colorOf(x) == BLACK) {         if  (x == leftOf(parentOf(x))) {             Entry<K,V> sib = rightOf(parentOf(x));             if  (colorOf(sib) == RED) {                 setColor(sib, BLACK);                                    setColor(parentOf(x), RED);                              rotateLeft(parentOf(x));                                 sib = rightOf(parentOf(x));                          }             if  (colorOf(leftOf(sib))  == BLACK &&                 colorOf(rightOf(sib)) == BLACK) {                 setColor(sib, RED);                                      x = parentOf(x);                                     } else  {                 if  (colorOf(rightOf(sib)) == BLACK) {                     setColor(leftOf(sib), BLACK);                            setColor(sib, RED);                                      rotateRight(sib);                                        sib = rightOf(parentOf(x));                          }                 setColor(sib, colorOf(parentOf(x)));                     setColor(parentOf(x), BLACK);                            setColor(rightOf(sib), BLACK);                           rotateLeft(parentOf(x));                                 x = root;                                            }         } else  {              Entry<K,V> sib = leftOf(parentOf(x));             if  (colorOf(sib) == RED) {                 setColor(sib, BLACK);                                    setColor(parentOf(x), RED);                              rotateRight(parentOf(x));                                sib = leftOf(parentOf(x));                           }             if  (colorOf(rightOf(sib)) == BLACK &&                 colorOf(leftOf(sib)) == BLACK) {                 setColor(sib, RED);                                      x = parentOf(x);                                     } else  {                 if  (colorOf(leftOf(sib)) == BLACK) {                     setColor(rightOf(sib), BLACK);                           setColor(sib, RED);                                      rotateLeft(sib);                                         sib = leftOf(parentOf(x));                           }                 setColor(sib, colorOf(parentOf(x)));                     setColor(parentOf(x), BLACK);                            setColor(leftOf(sib), BLACK);                            rotateRight(parentOf(x));                                x = root;                                            }         }     }     setColor(x, BLACK); } 
TreeSet 前面已经说过TreeSet 是对TeeMap 的简单包装,对TreeSet 的函数调用都会转换成合适的TeeMap 方法,因此TreeSet 的实现非常简单。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 public  class  TreeSet <E > extends  AbstractSet <E >    implements  NavigableSet <E >, Cloneable , java .io .Serializable  	......     private  transient  NavigableMap<E,Object> m;          private  static  final  Object PRESENT = new  Object();     public  TreeSet ()           this .m = new  TreeMap<E,Object>();     }     ......     public  boolean  add (E e)           return  m.put(e, PRESENT)==null ;     }     ...... } 
参考