• TreeMap源码分析


      TreeMap基于红黑树实现。查看“键”或“键值对”时,它们会被排序(次序由Comparable或Comparator决定)。TreeMap的特点在于,所得到的结果是经过排序的。TreeMap是唯一的带有subMap()方法的Map,它可以返回一个子树。 

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

      Comparable接口:

    1 public interface Comparable<T> {
    2     public int compareTo(T o);
    3 }
    

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

      Comparamtor接口:

    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。

      小结一下,实现Comparable结构的类可以和其他对象进行比较,即实现Comparable可以进行比较的类。而实现Comparator接口的类是比较器,用于比较两个对象的大小。

      下面正式分析TreeMap的源码。

      既然TreeMap底层使用的是树结构,那么必然有表示节点的对象。下面先看TreeMap中表示节点的内部类Entry。

     1 static final class Entry<K,V> implements Map.Entry<K,V> {
     3    K key;//键值对的“键”
     5     V value;//键值对的“值”
     7     Entry<K,V> left = null;
     9     Entry<K,V> right = null;
    11     Entry<K,V> parent;
    13     boolean color = BLACK;//红黑树的节点表示颜色的属性
    14     //根据给定的键、值、父节点构造一个节点,颜色为默认的黑色
    17     Entry(K key, V value, Entry<K,V> parent) {
    18         this.key = key;
    19         this.value = value;
    20         this.parent = parent;
    21     }
    23     public K getKey() {
    24         return key;
    25     }
    27     public V getValue() {
    28         return value;
    29     }
    33     public V setValue(V value) {
    34         V oldValue = this.value;
    35         this.value = value;
    36         return oldValue;
    37     }
    39     public boolean equals(Object o) {
    40         if (!(o instanceof Map.Entry))
    41             return false;
    42         Map.Entry<?,?> e = (Map.Entry<?,?>)o;
    43         return valEquals(key,e.getKey()) && valEquals(value,e.getValue());
    44     }
    46     public int hashCode() {
    47         int keyHash = (key==null ? 0 : key.hashCode());
    48         int valueHash = (value==null ? 0 : value.hashCode());
    49         return keyHash ^ valueHash;
    50     }
    51     public String toString() {
    52         return key + "=" + value;
    53     }
    54 }
    

      上面的Entry类比较简单,实现了树节点的必要内容,提供了hashCode方法等。下面看TreeMap类的定义。

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

      上面只有一个接口需要说明,那就是NavigableMap接口。NavigableMap接口扩展的SortedMap,具有了针对给定搜索目标返回最接近匹配项的导航方法。方法lowerEntry、 floorEntry、ceilingEntry和higherEntry分别返回与小于、小于等于、大于等于、大于给定键的键关联的Map.Entry 对象,如果不存在这样的键,则返回null。类似地,方法lowerKey、floorKey、ceilingKey和higherKey只返回关联的键。所有这些方法是为查找条目而不是遍历条目而设计的。

      下面是TreeMap的属性:

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

      注释中已经给出了属性的解释,下面看TreeMap的构造方法。

     

     // 构造方法一,默认的构造方法,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 }
    

      TreeMap提供了四个构造方法,已经在注释中给出说明。构造方法中涉及到的方法在下文中会有介绍。

      下面从put/get方法开始,逐个分析TreeMap的方法。

      put(K key, V value)

     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         }
    11         int cmp;//记录比较结果
    12         Entry<K,V> parent; 
    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);
    24                 if (cmp < 0)
    25                     t = t.left;
    27                 else if (cmp > 0)
    28                     t = t.right;
    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;
    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     }
    

      首先一点通性是TreeMap的put方法和其他Map的put方法一样,向Map中加入键值对,若原先“键(key)”已经存在则替换“值(value)”,并返回原先的值。

      在put(K key,V value)方法的末尾调用了fixAfterInsertion(Entry<K,V> x)方法,这个方法负责在插入节点后调整树结构和着色,以满足红黑树的要求。

    1. 每一个节点或者着成红色,或者着成黑色。
    2. 根是黑色的。
    3. 如果一个节点是红色的,那么它的子节点必须是黑色的。
    4. 一个节点到一个null引用的每一条路径必须包含相同数量的黑色节点。

      在看fixAfterInsertion(Entry<K,V> x)方法前先看一个红黑树的内容:红黑树不是严格的平衡二叉树,它并不严格的保证左右子树的高度差不超过1,但红黑树高度依然是平均log(n),且最坏情况高度不会超过2log(n),所以它算是平衡树。下面看具体实现代码。

      fixAfterInsertion(Entry<K,V> x)

     1 private void fixAfterInsertion(Entry<K,V> x) {  
     3     x.color = RED;//插入节点默认为红色
     4     //循环条件是x不为空、不是根节点、父节点的颜色是红色(如果父节点不是红色,则没有连续的红色节点,不再调整)
     5     while (x != null && x != root && x.parent.color == RED) {
     6         //x节点的父节点p(记作p)是其父节点pp(p的父节点,记作pp)的左孩子(pp的左孩子)
     7         if (parentOf(x) == leftOf(parentOf(parentOf(x)))) {
     8             //获取pp节点的右孩子r
     9             Entry<K,V> y = rightOf(parentOf(parentOf(x)));
    10             // pp右孩子的颜色是红色(colorOf(Entry e)方法在e为空时返回BLACK),不需要进行旋转操作(因为红黑树不是严格的平衡二叉树)
    11             if (colorOf(y) == RED) {
    13                 setColor(parentOf(x), BLACK); //将父节点设置为黑色
    15                 setColor(y, BLACK);//y节点,即r设置成黑色   
    17                 setColor(parentOf(parentOf(x)), RED); //pp节点设置成红色
    19                 x = parentOf(parentOf(x)); //x“移动”到pp节点
    20             }else{ //父亲的兄弟是黑色的,根据是"内部”还是"外部"的情况决定是双旋转还是单旋转
    21                 //x节点是父节点的右孩子(左-右"插入的情况,需要进行双旋转处理)
    22                 if (x == rightOf(parentOf(x))) { 
    24                     x = parentOf(x); //x移动到它的父节点
    26                     rotateLeft(x); //左旋操作
    27                 }
    29                 setColor(parentOf(x), BLACK); //x的父节点设置成黑色
    31                 setColor(parentOf(parentOf(x)), RED); //x的父节点的父节点设置成红色
    33                 rotateRight(parentOf(parentOf(x))); //右旋操作
    34             }
    35         } else {
    36             //获取x的父节点(记作p)的父节点(记作pp)的左孩子
    37             Entry<K,V> y = leftOf(parentOf(parentOf(x)));
    38             //y节点是红色的
    39             if (colorOf(y) == RED) { 
    41                 setColor(parentOf(x), BLACK); //x的父节点,即p节点,设置成黑色
    43                 setColor(y, BLACK);// y节点设置成黑色
    45                 setColor(parentOf(parentOf(x)), RED);//pp节点设置成红色
    47                 x = parentOf(parentOf(x)); //x移动到pp节点
    48             } else {
    49                 //x是父节点的左孩子(因为上面已近确认p是pp的右孩子,所以这是一个“内部,右-左”插入的情况,需要进行双旋转处理),
    50                 if (x == leftOf(parentOf(x))) {
    52                     x = parentOf(x);//x移动到父节点
    54                     rotateRight(x);//右旋操作
    55                 }
    57                 setColor(parentOf(x), BLACK);//x的父节点设置成黑色
    59                 setColor(parentOf(parentOf(x)), RED);//x的父节点的父节点设置成红色
    61                 rotateLeft(parentOf(parentOf(x)));60 //左旋操作
    62             }
    63         }
    64     }
    66     root.color = BLACK;//根节点为黑色
    67 }
    

      fixAfterInsertion(Entry<K,V> x)方法涉及到了左旋和右旋的操作,下面是左旋的代码及示意图(右旋操作类似,就不给出代码和示意图了)。

     1 private void rotateLeft(Entry<K,V> p) {
     3     if (p != null) {
     4         Entry<K,V> r = p.right;
     5         p.right = r.left;
     6         if (r.left != null)
     7             r.left.parent = p;
     8         r.parent = p.parent;
     9         if (p.parent == null)
    10             root = r;
    11         else if (p.parent.left == p)
    12             p.parent.left = r;
    13         else
    14             p.parent.right = r;
    15         r.left = p;
    16         p.parent = r;
    17     }
    18 }
    

      看完put操作,下面来看get操作相关的内容。

      get(Object key)

    1 public V get(Object key) {
    2     Entry<K,V> p = getEntry(key);
    3     return (p==null ? null : p.value);
    4 }
    

      get(Object key)通过key获取对应的value,它通过调用getEntry(Object key)获取节点,若节点为null则返回null,否则返回节点的value值。下面是getEntry(Object key)的内容,来看它是怎么寻找节点的。

      getEntry(Object key)

     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;  
    11     Entry<K,V> p = root;//获取根节点
    12     //对树进行遍历查找节点
    13     while (p != null) {
    15         int cmp = k.compareTo(p.key);//把key和当前节点的key进行比较
    17         if (cmp < 0)
    19             p = p.left;
    21         else if (cmp > 0)
    23         p = p.right;
    25         else
    27             return p;
    28         }
    30     return null;//没找到则返回null
    31 }
    

      上面主要是处理实现了可比较接口的情况,而有比较器的情况在getEntryUsingComparator(Object key)中处理了,下面来看处理的代码。

      getEntryUsingComparator(Object key)

     1 final Entry<K,V> getEntryUsingComparator(Object key) {
     2     K k = (K) key;
     4   Comparator<? super K> cpr = comparator;//获取比较器
     5   //其实在调用此方法的get(Object key)中已经对比较器为null的情况进行判断,这里是防御性的判断
     6   if (cpr != null) 
     8         Entry<K,V> p = root;//获取根节点
    10         while (p != null) {
    12             int cmp = cpr.compare(k, p.key);
    14             if (cmp < 0)
    16                 p = p.left;
    18             else if (cmp > 0)
    20                 p = p.right;
    22             else
    24                 return p;
    25         }
    26   }
    28    return null;//没找到key值对应的节点,返回null
    29 }
    

      看完添加(put)和获取(get),下面来看删除(remove、clear)。

      remove(Object key)

     1 public V remove(Object key) {
     2     //通过getEntry(Object key)获取节点getEntry(Object key)方法已经在上面介绍过了
     3   Entry<K,V> p = getEntry(key);
     5    if (p == null)
     6         return null;
     8   V oldValue = p.value;
    10   deleteEntry(p);
    12     return oldValue;
    13 }
    

      真正实现删除节点的内容在deleteEntry(Entry e)中,涉及到树结构的调整等。remove(Object key)只是获取要删除的节点并返回被删除节点的value。下面来看deleteEntry(Entry e)的内容。

      deleteEntry(Entry e)

     1 private void deleteEntry(Entry<K,V> p){
     3    modCount++;
     5     size--;
     7   //p有左右两个孩子的情况标记①
     8    if(p.left != null && p.right != null){
     9         //获取继承者节点(有两个孩子的情况下,继承者肯定是右孩子或右孩子的最左子孙)
    10         Entry<K,V> s = successor (p);
    11         //使用继承者s替换要被删除的节点p,将继承者的key和value复制到p节点,之后将p指向继承者
    12         p.key = s.key;
    13         p.value = s.value;
    14         p = s;
    15     } 
    18   //开始修复被移除节点处的树结构
    19   //如果p有左孩子,取左孩子,否则取右孩子标记②
    20     Entry<K,V> replacement=(p.left != null ? p.left : p.right);
    21     if(replacement!= null){
    23         replacement.parent = p.parent;
    24         //p节点没有父节点,即p节点是根节点
    25         if(p.parent == null)
    27             root = replacement;//将根节点替换为replacement节点
    29         else if(p == p.parent.left)//p是其父节点的左孩子
    32             p.parent.left  = replacement;//将p的父节点的left引用指向replacement
    33         else
    35             p.parent.right = replacement;//如果p是其父节点的右孩子,将父节点的right引用指向replacement
    36         //解除p节点到其左右孩子和父节点的引用
    37         p.left = p.right = p.parent = null;
    38         if(p.color == BLACK)
    39             //在删除节点后修复红黑树的颜色分配
    40             fixAfterDeletion(replacement);
    41      }else if(p.parent == null){ 
    42      /*进入这块代码则说明p节点就是根节点(这块比较难理解,如果标记①处p有左右孩子,
           *则找到的继承节点s是p的一个祖先节点或右孩子或右孩子的最左子孙节点,
           *他们要么有孩子节点,要么有父节点,所以如果进入这块代码,则说明标记①
            *除的p节点没有左右两个孩子。没有左右孩子,则有没有孩子、有一个右孩子、
           *有一个左孩子三种情况,三种情况中只有没有孩子的情况会使标记②的
           *if判断不通过,所以p节点只能是没有孩子,加上这里的判断,p没
           *有父节点,所以p是一个独立节点,也是树种的唯一节点……有点难理解,
           *所以将根节点设置为null即实现了对该节点的删除 */
    43           root = null;
    44         }else{ /* 标记②的if判断没有通过说明被删除节点没有孩子,
                        * 或它有两个孩子但它的继承者没有孩子。如果是被删除节点没有孩子,
                        * 说明p是个叶子节点,则不需要找继承者,直接删除该节点。如果是有两个孩子,
                        * 那么继承者肯定是右孩子或右孩子的最左子孙 */
    45           if(p.color == BLACK)    
    47               fixAfterDeletion(p);//调整树结构
    48            //这个判断也一定会通过,因为p.parent如果不是null则在上面的else if块中已经被处理
    49           if(p.parent != null) {
    50               //p是一个左孩子
    51               if (p == p.parent.left)
    53                   p.parent.left = null;//删除父节点对p的引用
    54               else if(p == p.parent.right)// p是一个右孩子
    56                   p.parent.right = null;// 删除父节点对p的引用
    58               p.parent = null;//删除p节点对父节点的引用
    59         }
    60     }
    61 }                               
    

       deleteEntry(Entry e)方法中主要有两个方法调用需要分析:successor(Entry<K,V> t)和fixAfterDeletion(Entry<K,V> x)。

      successor(Entry<K,V> t)返回指定节点的继承者。分三种情况处理,第一。t节点是个空节点:返回null;第二,t有右孩子:找到t的右孩子中的最左子孙节点,如果右孩子没有左孩子则返回右节点,否则返回找到的最左子孙节点;第三,t没有右孩子:沿着向上(向跟节点方向)找到第一个自身是一个左孩子的节点或根节点,返回找到的节点。下面是具体代码分析的注释。

     1 static <K,V> TreeMap.Entry<K,V> successor(Entry<K,V> t) {
     2     //如果t本身是一个空节点,返回null
     3     if (t == null)
     4         return null;
     5     // 如果t有右孩子,找到右孩子的最左子孙节点
     6     else if(t.right!= null) {
     7         Entry<K,V> p = t.right;
     8         // 获取p节点最左的子孙节点,如果存在的话
     9         while (p.left != null)
    10             p = p.left;
    11         // 返回找到的继承节点
    12         return p;
    13     }else{
           //t不为null且没有右孩子 14 Entry<K,V> p = t.parent; 15 Entry<K,V> ch = t; 16 //沿着右孩子向上查找继承者,直到根节点或找到节点ch是其父节点的左孩子的节点 17 while(p != null && ch == p.right) { 18 ch = p; 19 p = p.parent; 20 } 21 return p; 22 } 23 }

      与添加节点之后的修复类似的是,TreeMap 删除节点之后也需要进行类似的修复操作,通过这种修复来保证该排序二叉树依然满足红黑树特征。大家可以参考插入节点之后的修复来分析删除之后的修复。 TreeMap 在删除之后的修复操作由 fixAfterDeletion(Entry<K,V> x) 方法提供,该方法源代码如下:

    1 private void fixAfterDeletion(Entry<K,V> x) {
     2 //循环处理,条件为x不是root节点且是黑色的(因为红色不会对红黑树的性质造成破坏,所以不需要调整)
     3 while (x != root && colorOf(x) == BLACK) {
     4        //x是一个左孩子
     5         if (x == leftOf(parentOf(x))) {
     6             //获取x的兄弟节点sib
     7             Entry<K,V> sib = rightOf(parentOf(x));
     8             //sib是红色的
     9             if (colorOf(sib) == RED) {
    11                 setColor(sib, BLACK);//将sib设置为黑色
    13                 setColor(parentOf(x), RED);//将父节点设置成红色
    15                 rotateLeft(parentOf(x));//左旋父节点
    16                 //sib移动到旋转后x的父节点p的右孩子(参见左旋示意图,获取的节点是旋转前p的右孩子r的左孩子rl)
    17                 sib = rightOf(parentOf(x));
    18             }
    19             //sib的两个孩子的颜色都是黑色(null返回黑色)
    20             if (colorOf(leftOf(sib))  == BLACK &&
    21                 colorOf(rightOf(sib)) == BLACK) {
    23                 setColor(sib, RED);//将sib设置成红色
    25                 x = parentOf(x);//x移动到x的父节点
    26             }else{//sib的左右孩子都是黑色的不成立
    27                 //sib的右孩子是黑色的
    28                 if (colorOf(rightOf(sib)) == BLACK) {
    30                     setColor(leftOf(sib), BLACK);//将sib的左孩子设置成黑色   
    32                     setColor(sib, RED);//sib节点设置成红色
    34                     rotateRight(sib);//右旋操作
    36                     sib = rightOf(parentOf(x));//sib移动到旋转后x父节点的右孩子
    37                 }
    38                 //sib设置成和x的父节点一样的颜色
    39                 setColor(sib, colorOf(parentOf(x)));
    40                 //x的父节点设置成黑色
    41                 setColor(parentOf(x), BLACK);
    42                 //sib的右孩子设置成黑色
    43                 setColor(rightOf(sib), BLACK);
    45                 rotateLeft(parentOf(x));//左旋操作
    47                 x = root;//设置调整完的条件:x = root跳出循环
    48             }
    49         } else { // x是一个右孩子
    51             Entry<K,V> sib = leftOf(parentOf(x));//获取x的兄弟节点
    52             //如果sib是红色的
    53             if (colorOf(sib) == RED) {           
    55                 setColor(sib, BLACK);//将sib设置为黑色
    57                 setColor(parentOf(x), RED);//将x的父节点设置成红色
    59                 rotateRight(parentOf(x));//右旋
    61                 sib = leftOf(parentOf(x));//sib移动到旋转后x父节点的左孩子
    62             }
    63             //sib的两个孩子的颜色都是黑色(null返回黑色)
    64             if (colorOf(rightOf(sib)) == BLACK &&
    65                 colorOf(leftOf(sib)) == BLACK) {
    67                 setColor(sib, RED);//sib设置为红色
    68                 //x移动到x的父节点
    69                 x = parentOf(x);
    70             } else { // ib的两个孩子的颜色都是黑色(null返回黑色)不成立
    71                 //sib的左孩子是黑色的,或者没有左孩子
    72                 if (colorOf(leftOf(sib)) == BLACK) {
    74                     setColor(rightOf(sib), BLACK);//将sib的右孩子设置成黑色
    76                     setColor(sib, RED);//sib节点设置成红色
    78                     rotateLeft(sib);//左旋
    80                     sib = leftOf(parentOf(x));//sib移动到x父节点的左孩子
    81                 }
    83                 setColor(sib, colorOf(parentOf(x)));//sib设置成和x的父节点一个颜色
    85                 setColor(parentOf(x), BLACK);//x的父节点设置成黑色
    87                 setColor(leftOf(sib), BLACK);//sib的左孩子设置成黑色
    89                 rotateRight(parentOf(x));//右旋 
    91                 x = root;//设置跳出循环的标识
    92             }
    93         }
    94     }
    95     // 将x设置为黑色
    96     setColor(x, BLACK);
    97 }
    

      clear()

    1 public void clear() {
    2     modCount++;
    3     size = 0;
    4     root = null;
    5 }
    

      clear()方法很简单,只是记录结构修改次数,将size修改为0,将root设置为null,这样就没法通过root访问树的其他节点,所以数的内容会被GC回收。

      containKey(Object key)

    1 public boolean containsKey(Object key) {
    2     return getEntry(key) != null;
    3 }
    

      这个方法判断获取key对应的节点是否为空,getEntry(Object key)方法已经在上面介绍过了。

      contain(Object value)

    1 public boolean containsValue(Object value) {
    2     // 通过e = successor(e)实现对树的遍历
    3     for (Entry<K,V> e = getFirstEntry(); e != null; e = successor(e))
    4     // 判断节点值是否和value相等
    5         if (valEquals(value, e.value))
    6             return true;
    7     // 默认返回false
    8     return false;
    9 }
    

      contain(Object value)涉及到了getFirstEntry()方法和successor(Entry<K,V> e)。getFirstEntry()是获取第一个节点,successor(Entry<K,V> e)是获取节点e的继承者,在for循环中配合使用getFirstEntry()方法和successor(Entry<K,V> e)及e!=null是遍历树的一种方法。

      下面介绍getFirstEntry()方法。

      getFirstEntry()

    1 final Entry<K,V> getFirstEntry() {
    2     Entry<K,V> p = root;
    3     if (p != null)
    4         while (p.left != null)
    5             p = p.left;
    6     return p;
    7 }
     

      从名字上看是获取第一个节点,实际是获取的整棵树中“最左”的节点(第一个节点具体指哪一个节点和树的遍历次序有关,如果是先根遍历,则第一个节点是根节点)。又因为红黑树是排序的树,所以“最左”的节点也是值最小的节点。上面是getFirstEntry()方法,下面介绍getLastEntry()方法。

      getLastEntry()

    1 final Entry<K,V> getLastEntry() {
    2     Entry<K,V> p = root;
    3     if (p != null)
    4         while (p.right != null)
    5             p = p.right;
    6     return p;
    7 }

      getLastEntry()和getFirstEntry()对应,获取的是“最右”的节点。TreeMap中提供了获取并移除最小和最大节点的两个方法:pollFirstEntry()和pollLastEntry()。

      pollFirstEntry()

    1 public Map.Entry<K,V> pollFirstEntry() {
    2     Entry<K,V> p = getFirstEntry();
    3     Map.Entry<K,V> result = exportEntry(p);
    4     if (p != null)
    5         deleteEntry(p);
    6     return result;
    7 }
    

      pollLastEntry()

    1 public Map.Entry<K,V> pollLastEntry() {
    2     Entry<K,V> p = getLastEntry();
    3     Map.Entry<K,V> result = exportEntry(p);
    4     if (p != null)
    5         deleteEntry(p);
    6     return result;
    7 }
    

      pollFirstEntry()和pollLastEntry()分别通过getFirstEntry()和getLastEntry()获取节点,exportEntry(TreeMap.Entry<K,V> e)应该是保留这个对象用于在删除这个节点后返回。具体实现看下面的代码。

    1 static <K,V> Map.Entry<K,V> exportEntry(TreeMap.Entry<K,V> e) {
    2     return e == null? null :
    3         new AbstractMap.SimpleImmutableEntry<K,V>(e);
    4 }
    

      返回了一个SimpleImmutableEntry对象,调用的构造方法如下:

    1 public SimpleImmutableEntry(Entry<? extends K, ? extends V> entry) {
    2     this.key   = entry.getKey();
    3     this.value = entry.getValue();
    4 }
    

      可以看到返回的节点内容只包含key和value。

      下面看其他具体的获取键、值、键值对的方法。

    1 public Map.Entry<K,V> ceilingEntry(K key) {
    2     return exportEntry(getCeilingEntry(key));
    3 }
    4 public K ceilingKey(K key) {
    5     return keyOrNull(getCeilingEntry(key));
    6 }

      上面这两个方法很简单,只是对exportEntry和keyOrNull的调用。keyOrNull根据传入的Entry是否为null,选择方法null或Entry的key。

     1 // 获取最小的节点的key
     2 public K firstKey() {
     3     return key(getFirstEntry());
     4 }
     5 // 获取最大节点的key
     6 public K lastKey() {
     7     return key(getLastEntry());
     8 }
     9 // 获取最小的键值对
    10 public Map.Entry<K,V> firstEntry() {
    11     return exportEntry(getFirstEntry());
    12 }
    13 // 获取最大的键值对
    14 public Map.Entry<K,V> lastEntry() {
    15     return exportEntry(getLastEntry());
    16 }
     

      这几个方法涉及到的内容都在上面介绍过了,就不在说明了。

     1 public Map.Entry<K,V> floorEntry(K key) {
     2     return exportEntry(getFloorEntry(key));
     3 }
     4 public K floorKey(K key) {
     5     return keyOrNull(getFloorEntry(key));
     6 }
     7 public Map.Entry<K,V> higherEntry(K key) {
     8     return exportEntry(getHigherEntry(key));
     9 }
    10 public K higherKey(K key) {
    11     return keyOrNull(getHigherEntry(key));
    12 }
    

      这几个获取key的Entry的方法都是对getFloorEntry和getHigherEntry的处理。下面介绍这两个方法。

      getFloorEntry(K key)

     1 final Entry<K,V> getFloorEntry(K key) {
     2     // 获取根节点
     3 Entry<K,V> p = root;
     4 // 不是空树,最树进行遍历
     5     while (p != null) {
     6         int cmp = compare(key, p.key);
     7         // key较大
     8         if (cmp > 0) {
     9             // 找到节点有右孩子,则继续向右孩子遍历
    10             if (p.right != null)
    11                 p = p.right;
    12             else// 没有右孩子,那么p节点就是树中比key值比传入key值小且最接近传入key的节点,就是要找的节点
    13                 return p;
    14         } else if (cmp < 0) {// key值较小
    15             // 有左孩子向左孩子遍历
    16             if (p.left != null) {
    17                 p = p.left;
    18             } else {// 没有左孩子,这个节点比key值大,返回内容是向上寻找到的根节点或比传入key值小的最后一个节点
    19                 Entry<K,V> parent = p.parent;
    20                 Entry<K,V> ch = p;
    21                 while (parent != null && ch == parent.left) {
    22                     ch = parent;
    23                     parent = parent.parent;
    24                 }
    25                 return parent;
    26             }
    27         } else // key值相等
    28             return p;
    29     }
    30     return null;
    31 }

      getHigherEntry(K key)

     1 final Entry<K,V> getHigherEntry(K key) {
     2     Entry<K,V> p = root;
     3     while (p != null) {
     4         int cmp = compare(key, p.key);
     5         if (cmp < 0) {
     6             if (p.left != null)
     7                 p = p.left;
     8             else
     9                 return p;
    10         } else {
    11             if (p.right != null) {
    12                 p = p.right;
    13             } else {
    14                 Entry<K,V> parent = p.parent;
    15                 Entry<K,V> ch = p;
    16                 while (parent != null && ch == parent.right) {
    17                     ch = parent;
    18                     parent = parent.parent;
    19                 }
    20                 return parent;
    21             }
    22         }
    23     }
    24     return null;
    25 }
    

      getFloorEntry和getHigherEntry方法遍历和寻找节点的方法类似,区别在于getFloorEntry寻找的是小于等于,优先返回小于的节点,而getHigherEntry寻找的是严格大于的节点,不包括等于的情况。

     
  • 相关阅读:
    去除字符串重复数据 分类: python基础学习 2013-08-08 17:43 171人阅读 评论(0) 收藏
    python中计时工具timeit模块的基本用法 分类: python python基础学习 2013-08-08 10:05 2072人阅读 评论(0) 收藏
    socket通信简介 分类: socket 2013-08-06 17:49 209人阅读 评论(0) 收藏
    AF_INET和PF_INET的细微不同 分类: socket 2013-08-06 17:35 264人阅读 评论(0) 收藏
    查看linux操作系统版本号 分类: ubuntu 2013-08-05 10:14 265人阅读 评论(0) 收藏
    python yield生成器 分类: python基础学习 2013-08-04 15:40 324人阅读 评论(0) 收藏
    html 元素 2013-08-04 14:53 236人阅读 评论(0) 收藏
    UTF8默认编码下的MYSQL命令框中输入汉字显示乱码的问题 分类: 测试 2013-08-02 14:16 371人阅读 评论(0) 收藏
    os.path模块学习 2013-07-30 17:46 193人阅读 评论(0) 收藏
    python 操作windows下的目录 分类: python python基础学习 2013-07-30 17:30 274人阅读 评论(0) 收藏
  • 原文地址:https://www.cnblogs.com/wxgblogs/p/5513313.html
Copyright © 2020-2023  润新知