您的位置:首页 > 编程语言 > Java开发

JDK源码学习之TreeMap.java分析

2016-04-28 22:36 686 查看
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寻找的是严格大于的结点,不包括等于的情况。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: