TreeMap源码分析——基础分析
常见的数据结构有数组、链表、树。集合类中有基于数组的ArrayList,基于链表的LinkedList,还有链表和数组结合的HashMap。
Treemap基于红黑树实现。查看“键”或“键值对”时,他们会被排序(次序 由Comparable或Comparator决定)。
TreeMap的特点在于,所得到的结果是经过排序的。TreeMap是唯一的带有subMap()方法的Map,它可以返回一个子树。
在介绍TreeMap前先介绍Comparable和Comparator接口。
Comparable接口:
public interface Comparable<T>{
public int compareTo(T o);
}
Comparable接口支持泛型,只有一个方法,该方法返回负数、零、正数分别表示当前对象“小于”、“等于”、“大于”传入对象o。
Comparator接口:
public interface Comparator<T>{
int compare(T o1, T o2);
boolean equals(Object obj);
}
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)表示数学上的sigmoid函数,该函数根据expression的值是负数、零或者正数,分别返回-1、0或1.
小结一下,实现Comparable结构的类可以和其他对象进行比较,即实现Comparable可以进行比较的类。、
而实现Comparator接口的类是比较器,用于比较两个对象的大小。
TreeMap
TreeMap类定义:
public class TreeMap<K, V> extends AbstractMap<K, V> implements NavigableMap<K, V>, Cloneable, java.io.Serializable
NavigableMap接口扩展的SortedMap,具有了针对给定搜索目标返回最接近匹配项的导航方法。
方法lowerEntry、floorEntry、ceilingEntry和higherEntry分别返回与小于、小于等于、大于等于、大于给定键的键关联的Map.Entry对象,如果不存在这样的键,则返回null。
类似地,方法lowerKey、floorKey、ceilingKey和higherKey只返回关联的键。
所有这些方法是为查找条目而不是遍历条目而设计的。
TreeMap的属性有:
//用于保持顺序的比较器,如果为空,使用自然序保持Key的顺序
private final Comparator<? super K> comparator;
//根节点
private transient Entry<K,V> root = null;
//树中的结点数量
private transient int size = 0;
//多次在集合类中提到了,用于记录结构的改变次数
private transient int modCount = 0;
TreeMap中的put()方法和其他Map的put()方法一样,向Map中加入键值对,若原先“键(Key)”已经存在则替换“值value”,并返回原先的值。
在put(K key, V value)方法的末尾调用了fixAfterInsertion(Entry<K, V> x)方法,这个方法负责在插入结点后调整树结构和着色,以满足红黑树的要求。
1.每个结点或者是红色,或者是黑色
2.根是黑色的
3.如果一个结点是红色的,那么它的子节点必须是黑色的
4.一个结点到一个null引用的每一条路径必须包含相同数量的黑色结点。
注意,红黑树不是严格的平衡二叉树,它并不严格的保证左右子树的高度差不超过1,但红黑树高度依然是平均log(n),
且最坏情况高度不会超过2log(n),所以它算是平衡树。
fixAfterInsertion(Entry<K, V> x)方法涉及到了左旋和右旋操作
TreeMap中的get(Object key)通过Key获取对应的value,它通过调用getEntry(Object key)获取结点,
若结点为null则返回null,否则返回结点的value值。
getEntry函数主要是处理实现了可比较接口的情况,而有比较器的情况则是调用了getEntryUsingComparator(Object key).
remove(Object key)只是获取要删除的结点并返回被删除结点的value。真正实现删除结点的内容是在deleteEntry(Entry e)中,设计到树结构的调整。
deleteEntry(Entry e)方法中主要有两个方法调用需要分析:successor(Entry<K,V> t)和fixAfterDeletion(Entry<K,V> x)。
successor(Entry<K,V> t)返回指定结点的后继结点。分三种情况处理,
第一,t结点是个空结点:返回null;
第二,t有右孩子:找到t的右孩子的最左子孙结点,如果右孩子没有左孩子则返回右结点,否则返回找到的最左子孙结点;
第三,t没有右孩子:沿着向上(向根节点方向)找到第一个自身是一个左孩子的结点或根节点,返回找到的结点
与添加结点之后的修复类似,TreeMap删除结点后也需要进行类似的修复操作,通过这种修复来保证该排序二叉树依然满足红黑树特征。
删除之后的修复有fixAfterDeletion(Entry<K,V> x)方法提供。
clear()方法只是记录结构修改次数,将Size修改为0,将root设置为null,这样就没法通过root访问树的其他结点,所以树的内容会被GC回收。
containsKey(Object key)判断获取Key对应的结点是否为空,调用getEntry(Object key)方法获得Key对应的对象。
containsValue(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()实际上是获取整棵树中“最左”的结点(第一个结点具体指哪一个结点和树的遍历次序有关,如果是先根遍历,则第一个结点是根节点)。
又因为红黑树是排序的树,所以“最左”的结点也是值最小的结点。
getLastEntry(),获取最右的结点
TreeMap中还提供了获取并移除最小和最大结点的两个方法:pollFirstEntry()和pollLastEntry(),分别通过getFirstEntry()和getLastEntry()获取结点,
ExportEntry(TreeMap.Entry<K, V> e)是创建一个用于返回的删除结点对象。
static <K,V> Map.Entry<K,V> exportEntry(TreeMap.Entry<K,V> e) {
return e == null? null :
new AbstractMap.SimpleImmutableEntry<K,V>(e);
}
返回了一个SimpleImmutableEntry对象,调用的构造方法如下:
public SimpleImmutableEntry(Entry<? extends K, ? extends V> entry) {
this.key = entry.getKey();
this.value = entry.getValue();
}
可以看到返回的结点内容只包含Key和value。
而其他具体的获取键、值、键值对的方法。
public Map.Entry<K,V> ceilingEntry(K key) {
return exportEntry(getCeilingEntry(key));
}
public K ceilingKey(K key) {
return keyOrNull(getCeilingEntry(key));
}
上面这两个方法只是对exportEntry和keyOrNull的调用。keyOrNull根据传入的Entry是否为null,选择返回null或Entry的Key
// 获取最小的节点的key
public K firstKey() {
return key(getFirstEntry());
}
// 获取最大节点的key
public K lastKey() {
return key(getLastEntry());
}
// 获取最小的键值对
public Map.Entry<K,V> firstEntry() {
return exportEntry(getFirstEntry());
}
// 获取最大的键值对
public Map.Entry<K,V> lastEntry() {
return exportEntry(getLastEntry());
}
getFloorEntry和getHigherEntry方法遍历和寻找结点的方法类似,区别在于getFloorEntry寻找的是小于等于,优先返回小于的结点,
而getHigherEntry寻找的是严格大于的结点,不包括等于的情况。