常见的数据结构有数组、链表,还有一种结构也很常见,那就是树。前面介绍的集合类有基于数组的ArrayList,有基于链表的LinkedList,还有链表和数组结合的HashMap,今天介绍基于树的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> { 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 */ 17 Entry(K key, V value, Entry<K,V> parent) { 18 this.key = key; 19 this.value = value; 20 this.parent = parent; 21 } 22 // 获取节点的key 23 public K getKey() { 24 return key; 25 } 26 // 获取节点的value 27 public V getValue() { 28 return value; 29 } 30 /** 31 * 修改并返回当前节点的value 32 */ 33 public V setValue(V value) { 34 V oldValue = this.value; 35 this.value = value; 36 return oldValue; 37 } 38 // 判断节点相等的方法(两个节点为同一类型且key值和value值都相等时两个节点相等) 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 } 45 // 节点的哈希值计算方法 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的属性:
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;
注释中已经给出了属性的解释,下面看TreeMap的构造方法。
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 }
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 } 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 >