java容器类---TreeMap、TreeSet

1、TreeMap 简介

TreeMap是基于红黑树实现的,这里只对红黑树做个简单的介绍,红黑树是一种特殊的二叉排序树,关于二叉排序树,红黑树通过一些限制,使其不会出现二叉树排序树中极端的一边倒的情况,相对二叉排序树而言,这自然提高了查询的效率。

红黑树的基本性质如下:
1、每个节点都只能是红色或者黑色
2、根节点是黑色
3、每个叶节点(NULL节点,空节点)是黑色的。
4、如果一个结点是红的,则它两个子节点都是黑的。也就是说在一条路径上不能出现相邻的两个红色结点。
5、从任一节点到其每个叶子的所有路径都包含相同数目的黑色节点。

在介绍TreeMap前先介绍Comparable和Comparator接口。 

Comparable接口:

 

[java] view plain copy
 
  1. public interface Comparable<T> {  
  2.      public int compareTo(T o);  
  3.  }  

Comparable接口支持泛型,只有一个方法,该方法返回负数、零、正数分别表示当前对象“小于”、“等于”、“大于”传入对象o。

 

Comparator接口:

 

[java] view plain copy
 
  1. public interface Comparator<T> {  
  2.  int compare(T o1, T o2);  
  3.  boolean equals(Object obj);  
  4.  }  

compare(T o1,T o2)方法比较o1和o2两个对象,o1“大于”o2,返回正数,相等返回零,“小于”返回负数。

equals(Object obj)返回true的唯一情况是obj也是一个比较器(Comparator)并且比较结果和此比较器的结果的大小次序是一致的。即comp1.equals(comp2)意味着sgn(comp1.compare(o1,o2))==sgn(comp2.compare(o1,o2))。
补充:符号sgn(expression)表示数学上的signum函数,该函数根据expression的值是负数、零或正数,分别返回-1、0或1。

 

1.1 数据结构

    TreeMap的排序是基于对key的排序实现的,它的每一个Entry代表红黑树的一个节点,Entry的数据结构如下:

 

[java] view plain copy
 
  1. static final class Entry<K,V> implements Map.Entry<K,V> {      
  2.      // 键      
  3.      K key;      
  4.      // 值      
  5.      V value;      
  6.      // 左孩子      
  7.      Entry<K,V> left = null;      
  8.      // 右孩子      
  9.      Entry<K,V> right = null;      
  10.      // 父节点      
  11.      Entry<K,V> parent;      
  12.      // 当前节点颜色      
  13.      boolean color = BLACK;      
  14.     
  15.      // 构造函数      
  16.      Entry(K key, V value, Entry<K,V> parent) {      
  17.          this.key = key;      
  18.          this.value = value;      
  19.          this.parent = parent;      
  20.      }      
  21.     
  22. ........    
  23. }  

1.2 继承关系

 

[java] view plain copy
 
  1. public class TreeMap<K,V>  
  2.      extends AbstractMap<K,V>  
  3.      implements NavigableMap<K,V>, Cloneable, java.io.Serializable  

NavigableMap接口扩展的SortedMap,具有了针对给定搜索目标返回最接近匹配项的导航方法。方法lowerEntry、floorEntry、ceilingEntry和higherEntry分别返回与小于、小于等于、大于等于、大于给定键的键关联的Map.Entry对象,如果不存在这样的键,则返回null。类似地,方法lowerKey、floorKey、ceilingKey和higherKey只返回关联的键。

 

1.3 成员变量

 

[java] view plain copy
 
  1. // 用于保持顺序的比较器,如果为空的话使用自然顺保持Key的顺序  
  2.  private final Comparator<? super K> comparator;  
  3.  // 根节点  
  4.  private transient Entry<K,V> root = null;  
  5.  // 树中的节点数量  
  6.  private transient int size = 0;  
  7.  // 多次在集合类中提到了,用于举了结构行的改变次数  
  8.  private transient int modCount = 0;   

2、TreeMap 构造函数

 

[java] view plain copy
 
  1. // 构造方法一,默认的构造方法,comparator为空,即采用自然顺序维持TreeMap中节点的顺序  
  2. public TreeMap() {  
  3.     comparator = null;  
  4. }  
  5. // 构造方法二,提供指定的比较器  
  6. public TreeMap(Comparator<? super K> comparator) {  
  7.     this.comparator = comparator;  
  8. }  
  9. // 构造方法三,采用自然序维持TreeMap中节点的顺序,同时将传入的Map中的内容添加到TreeMap中  
  10. public TreeMap(Map<? extends K, ? extends V> m) {  
  11.     comparator = null;  
  12.     putAll(m);  
  13. }  
  14. /**  
  15. *构造方法四,接收SortedMap参数,根据SortedMap的比较器维持TreeMap中的节点顺序,* 同时通过buildFromSorted(int size, Iterator it, java.io.ObjectInputStream str, V defaultVal)方* 法将SortedMap中的内容添加到TreeMap中 
  16. */  
  17. public TreeMap(SortedMap<K, ? extends V> m) {  
  18.     comparator = m.comparator();  
  19.     try {  
  20.         buildFromSorted(m.size(), m.entrySet().iterator(), null, null);  
  21.     } catch (java.io.IOException cannotHappen) {  
  22.     } catch (ClassNotFoundException cannotHappen) {  
  23.     }  
  24. }  

 

构造方法一采用无参构造方法,不指定比较器,这时候,排序的实现要依赖key.compareTo()方法,因此key必须实现Comparable接口,并覆写其中的compareTo方法。
构造方法二采用带比较器的构造方法,这时候,排序依赖该比较器,key可以不用实现Comparable接口。

3、TreeMap 常用方法

3.1 构造TreeMap

 

[java] view plain copy
 
  1. // 将map中的全部节点添加到TreeMap中      
  2. public void putAll(Map<? extends K, ? extends V> map) {      
  3.     // 获取map的大小      
  4.     int mapSize = map.size();      
  5.     // 如果TreeMap的大小是0,且map的大小不是0,且map是已排序的“key-value对”      
  6.     if (size==0 && mapSize!=0 && map instanceof SortedMap) {      
  7.         Comparator c = ((SortedMap)map).comparator();      
  8.         // 如果TreeMap和map的比较器相等;      
  9.         // 则将map的元素全部拷贝到TreeMap中,然后返回!      
  10.         if (c == comparator || (c != null && c.equals(comparator))) {      
  11.             ++modCount;      
  12.             try {      
  13.                 buildFromSorted(mapSize, map.entrySet().iterator(),      
  14.                             null, null);      
  15.             } catch (java.io.IOException cannotHappen) {      
  16.             } catch (ClassNotFoundException cannotHappen) {      
  17.             }      
  18.             return;      
  19.         }      
  20.     }      
  21.     // 调用AbstractMap中的putAll();      
  22.     // AbstractMap中的putAll()又会调用到TreeMap的put()      
  23.     super.putAll(map);      
  24. }  

显然,如果Map里的元素是排好序的,就调用buildFromSorted方法来拷贝Map中的元素,而如果Map中的元素不是排好序的,就调用AbstractMap的putAll(map)方法,该方法源码如下 

 

 

[java] view plain copy
 
  1. public void putAll(Map<? extends K, ? extends V> m) {      
  2.     for (Map.Entry<? extends K, ? extends V> e : m.entrySet())      
  3.         put(e.getKey(), e.getValue());      
  4. }  

    很明显它是将Map中的元素一个个put(插入)到TreeMap中的,主要因为Map中的元素是无序存放的,因此要一个个插入到红黑树中,使其有序存放,并满足红黑树的性质。  

 

3.2 插入元素

插入操作即对应TreeMap的put方法,put操作实际上只需按照二叉排序树的插入步骤来操作即可,插入到指定位置后,再做调整,使其保持红黑树的特性。put源码的实现:

 

[java] view plain copy
 
  1. public V put(K key, V value) {  
  2.         Entry<K,V> t = root;  
  3.         if (t == null) {  
  4.         //如果根节点为null,将传入的键值对构造成根节点(根节点没有父节点,所以传入的父节点为null)  
  5.             root = new Entry<K,V>(key, value, null);  
  6.             size = 1;  
  7.             modCount++;  
  8.             return null;  
  9.         }  
  10.         // 记录比较结果  
  11.         int cmp;  
  12.         Entry<K,V> parent;  
  13.         // 分割比较器和可比较接口的处理  
  14.         Comparator<? super K> cpr = comparator;  
  15.         // 有比较器的处理  
  16.         if (cpr != null) {  
  17.             // do while实现在root为根节点移动寻找传入键值对需要插入的位置  
  18.             do {  
  19.                 // 记录将要被掺入新的键值对将要节点(即新节点的父节点)  
  20.                 parent = t;  
  21.                 // 使用比较器比较父节点和插入键值对的key值的大小  
  22.                 cmp = cpr.compare(key, t.key);  
  23.                 // 插入的key较大  
  24.                 if (cmp < 0)  
  25.                     t = t.left;  
  26.                 // 插入的key较小  
  27.                 else if (cmp > 0)  
  28.                     t = t.right;  
  29.                 // key值相等,替换并返回t节点的value(put方法结束)  
  30.                 else  
  31.                     return t.setValue(value);  
  32.             } while (t != null);  
  33.         }  
  34.         // 没有比较器的处理  
  35.         else {  
  36.             // key为null抛出NullPointerException异常  
  37.             if (key == null)  
  38.                 throw new NullPointerException();  
  39.             Comparable<? super K> k = (Comparable<? super K>) key;  
  40.             // 与if中的do while类似,只是比较的方式不同  
  41.             do {  
  42.                 parent = t;  
  43.                 cmp = k.compareTo(t.key);  
  44.                 if (cmp < 0)  
  45.                     t = t.left;  
  46.                 else if (cmp > 0)  
  47.                     t = t.right;  
  48.                 else  
  49.                     return t.setValue(value);  
  50.             } while (t != null);  
  51.         }  
  52.         // 没有找到key相同的节点才会有下面的操作  
  53.         // 根据传入的键值对和找到的“父节点”创建新节点  
  54.         Entry<K,V> e = new Entry<K,V>(key, value, parent);  
  55.         // 根据最后一次的判断结果确认新节点是“父节点”的左孩子还是又孩子  
  56.         if (cmp < 0)  
  57.             parent.left = e;  
  58.         else  
  59.             parent.right = e;  
  60.         // 对加入新节点的树进行调整  
  61.         fixAfterInsertion(e);  
  62.         // 记录size和modCount  
  63.         size++;  
  64.         modCount++;  
  65.         // 因为是插入新节点,所以返回的是null  
  66.         return null;  
  67.     }  

这里的fixAfterInsertion便是节点插入后对树进行调整的方法,这里不做介绍。
3.3 查找元素

 

[java] view plain copy
 
  1. final Entry<K,V> getEntry(Object key) {  
  2.     // 如果有比较器,返回getEntryUsingComparator(Object key)的结果  
  3.     if (comparator != null)  
  4.         return getEntryUsingComparator(key);  
  5.     // 查找的key为null,抛出NullPointerException  
  6.     if (key == null)  
  7.         throw new NullPointerException();  
  8.     // 如果没有比较器,而是实现了可比较接口  
  9.     Comparable<? super K> k = (Comparable<? super K>) key;  
  10.     // 获取根节点  
  11.     Entry<K,V> p = root;  
  12.     // 对树进行遍历查找节点  
  13.     while (p != null) {  
  14.         // 把key和当前节点的key进行比较  
  15.         int cmp = k.compareTo(p.key);  
  16.         // key小于当前节点的key  
  17.         if (cmp < 0)  
  18.             // p “移动”到左节点上  
  19.             p = p.left;  
  20.         // key大于当前节点的key  
  21.         else if (cmp > 0)  
  22.             // p “移动”到右节点上  
  23. p = p.right;  
  24.         // key值相等则当前节点就是要找的节点  
  25.         else  
  26.             // 返回找到的节点  
  27.             return p;  
  28.         }  
  29.     // 没找到则返回null  
  30.     return null;  
  31. }  

3.4 删除元素
删除操作及对应TreeMap的deleteEntry方法,deleteEntry方法同样也只需按照二叉排序树的操作步骤实现即可,删除指定节点后,再对树进行调整即可。deleteEntry方法的实现源码如下:

 

[java] view plain copy
 
  1. // 删除“红黑树的节点p”      
  2. private void deleteEntry(Entry<K,V> p) {      
  3.     modCount++;      
  4.     size--;      
  5.     
  6.     if (p.left != null && p.right != null) {      
  7.         Entry<K,V> s = successor (p);      
  8.         p.key = s.key;      
  9.         p.value = s.value;      
  10.         p = s;      
  11.     }     
  12.     
  13.     Entry<K,V> replacement = (p.left != null ? p.left : p.right);      
  14.     
  15.     if (replacement != null) {      
  16.         replacement.parent = p.parent;      
  17.         if (p.parent == null)      
  18.             root = replacement;      
  19.         else if (p == p.parent.left)      
  20.             p.parent.left  = replacement;      
  21.         else     
  22.             p.parent.right = replacement;      
  23.     
  24.         p.left = p.right = p.parent = null;      
  25.     
  26.         if (p.color == BLACK)      
  27.             fixAfterDeletion(replacement);      
  28.     } else if (p.parent == null) {     
  29.         root = null;      
  30.     } else {    
  31.         if (p.color == BLACK)      
  32.             fixAfterDeletion(p);      
  33.     
  34.         if (p.parent != null) {      
  35.             if (p == p.parent.left)      
  36.                 p.parent.left = null;      
  37.             else if (p == p.parent.right)      
  38.                 p.parent.right = null;      
  39.             p.parent = null;      
  40.         }      
  41.     }      
  42. }  

4、总结

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。

5、TreeSet

   TreeSet是基于TreeMap实现的,只是对应的节点中只有key,而没有value,因此对TreeMap比较了解的话,对TreeSet的理解就会非常容易。

 

参考来源:
【Java集合源码剖析】TreeMap源码剖析

TreeMap源码分析——基础分析(基于JDK1.6)

posted @ 2016-12-09 17:37  天涯海角路  阅读(149)  评论(0)    收藏  举报