TreeSet和TreeMap
一、TreeSet调试用例
package com.liu.collection;
import java.util.TreeSet;
public class TreeSet_ {
public static void main(String[] args) {
//treeset底层是treemap
// comparator 用于维护此树映射中的顺序的比较器,或
// 如果使用其键的自然顺序,则为null。
TreeSet<Object> treeSet = new TreeSet<>();
treeSet.add("aaa");
treeSet.add("zaa");
treeSet.add("daa");
treeSet.add("dba");
treeSet.add("gaa");
System.out.println(treeSet);
}
}
二、TreeSet的debug
进入TreeSet构造函数
发现默认采用自然排序
底层为TreeMap
/**
* 使用树的自然顺序构造一个新的空树映射
*钥匙。插入到映射中的所有键都必须实现{@link
*可比}接口。此外,所有此类钥匙必须
*相互可比的</em>:{@code k1.compareTo(k2)}不能抛出
*任何键{@code k1}的{@code ClassCastException}和
*{@code k2}在地图中。如果用户试图将密钥放入
*违反此约束的映射(例如,用户尝试
*将字符串键放入一个键为整数的映射中
*{@code put(对象键,对象值)}调用将抛出
*{@code ClassCastException}。
*/
public TreeSet() {
this(new TreeMap<E,Object>());
}
进入TreeMap构造函数
/**
* The comparator used to maintain order in this tree map, or
* null if it uses the natural ordering of its keys.
*用于维护此树映射中的顺序的比较器,或
*如果使用其键的自然顺序,则为null。
* @serial
*/
private final Comparator<? super K> comparator;
/**
* 使用树的自然顺序构造一个新的空树映射
*钥匙。插入到映射中的所有键都必须实现{@link
*可比}接口。此外,所有此类钥匙必须
*相互可比的</em>:{@code k1.compareTo(k2)}不能抛出
*任何键{@code k1}的{@code ClassCastException}和
*{@code k2}在地图中。如果用户试图将密钥放入
*违反此约束的映射(例如,用户尝试
*将字符串键放入一个键为整数的映射中
*{@code put(对象键,对象值)}调用将抛出
*{@code ClassCastException}。
*/
public TreeMap() {
comparator = null;
}
进入AbstractMap<K,V>
treemap继承了AbstractMap类
跳出AbstractMap<K,V>
跳出TreeMap
进入 this(new TreeMap<E,Object>());
/**
* Constructs a set backed by the specified navigable map.
*/
TreeSet(NavigableMap<E,Object> m) {
this.m = m;
}
跳出TreeSet
进入add方法
public boolean add(E e) {
return m.put(e, PRESENT)==null;
}
进入put方法(treemap的put方法)
public V put(K key, V value) {
//记录当前根结点的值
Entry<K,V> t = root;
//若根结点为空
if (t == null) {
//则比较key和key的大小
compare(key, key); // type (and possibly null) check
root = new Entry<>(key, value, null);
size = 1;
modCount++;
return null;
}
int cmp;
Entry<K,V> parent;
// split comparator and comparable paths
Comparator<? super K> cpr = comparator;
if (cpr != null) {
do {
parent = t;
cmp = cpr.compare(key, t.key);
if (cmp < 0)
t = t.left;
else if (cmp > 0)
t = t.right;
else
return t.setValue(value);
} while (t != null);
}
else {
if (key == null)
throw new NullPointerException();
@SuppressWarnings("unchecked")
Comparable<? super K> k = (Comparable<? super K>) key;
do {
parent = t;
cmp = k.compareTo(t.key);
if (cmp < 0)
t = t.left;
else if (cmp > 0)
t = t.right;
else
return t.setValue(value);
} while (t != null);
}
Entry<K,V> e = new Entry<>(key, value, parent);
if (cmp < 0)
parent.left = e;
else
parent.right = e;
fixAfterInsertion(e);
size++;
modCount++;
return null;
}
进入compare
/**
* Compares two keys using the correct comparison method for this TreeMap.
*/
@SuppressWarnings("unchecked")
final int compare(Object k1, Object k2) {
//若comparator为空,则
return comparator==null ? ((Comparable<? super K>)k1).compareTo((K)k2)
: comparator.compare((K)k1, (K)k2);
}
进入String类中的compareTo方法
自然排序
从前往后比较字符串的字母,直到遍历到第一个不相同的字母,返回两个字母差值的ASC码值
例题:
abc>acc acc<accs
a>bbb aaa=aaa
public int compareTo(String anotherString) {
//value可以调用compareTo方法,所以在调用前,就已经将value的值加载到String类中了
int len1 = value.length;
int len2 = anotherString.value.length;
//lim是两传入字符串的长度最小值
int lim = Math.min(len1, len2);
char v1[] = value;
char v2[] = anotherString.value;
int k = 0;
//知道遍历到较短字符串的末尾
while (k < lim) {
char c1 = v1[k];
char c2 = v2[k];
//若不相等,则返回差值的Asc
if (c1 != c2) {
return c1 - c2;
}
k++;
}
//若遍历到较短字符串的末尾,两字符串都相等,则返回两字符串的长度差值
return len1 - len2;
}
回到put方法
public V put(K key, V value) {
//记录当前根结点的值
Entry<K,V> t = root;
//若根结点为空
if (t == null) {
//则比较key和key的大小
compare(key, key); // type (and possibly null) check
//新建节点,并赋值给root
root = new Entry<>(key, value, null);
size = 1;
modCount++;
return null;
}
//若根结点不为空
int cmp;
Entry<K,V> parent;
// split comparator and comparable paths
Comparator<? super K> cpr = comparator;
if (cpr != null) {
do {
parent = t;
cmp = cpr.compare(key, t.key);
if (cmp < 0)
t = t.left;
else if (cmp > 0)
t = t.right;
else
return t.setValue(value);
} while (t != null);
}
else {//若comparator为null,且根结点不为null,add大于等于第二个结点时
if (key == null)
throw new NullPointerException();
@SuppressWarnings("unchecked")
Comparable<? super K> k = (Comparable<? super K>) key;
do {
//记录当前遍历到的根节点,每遍历一次,重新记录一次
parent = t;
//按照自然排序比较两key,并返回一个差值,用来判别大小
cmp = k.compareTo(t.key);
//若第一个值小
if (cmp < 0)
//t结点指向左孩子
t = t.left;
//若第一个值大于第二个值,
else if (cmp > 0)
//往根结点的右面找,符合二叉查找树的特点,给根节点重新赋值,
//t结点指向右孩子
t = t.right;
else//若key相等
//则替换为当前value
return t.setValue(value);
} while (t != null);//直到当前遍历到的结点为null
}
//新建结点,其父节点为parent
Entry<K,V> e = new Entry<>(key, value, parent);
//若 要加入的值 小于 遍历到的结点(parent) 的值
if (cmp < 0)
//将要加入的值放于遍历到的结点的左孩子
parent.left = e;
else
//放到右孩子
parent.right = e;
fixAfterInsertion(e);
size++;
modCount++;
return null;
}
红黑树Entry
双向的含有子结点和parent结点
static final class Entry<K,V> implements Map.Entry<K,V> {
K key;
V value;
Entry<K,V> left;
Entry<K,V> right;
Entry<K,V> parent;
boolean color = BLACK;
/**
* Make a new cell with given key, value, and parent, and with
* {@code null} child links, and BLACK color.
*/
Entry(K key, V value, Entry<K,V> parent) {
this.key = key;
this.value = value;
this.parent = parent;
}
修正红黑树的颜色(本节不做详述,下节详细叙述)
/** From CLR */
private void fixAfterInsertion(Entry<K,V> x) {
x.color = RED;
while (x != null && x != root && x.parent.color == RED) {
if (parentOf(x) == leftOf(parentOf(parentOf(x)))) {
Entry<K,V> y = rightOf(parentOf(parentOf(x)));
if (colorOf(y) == RED) {
setColor(parentOf(x), BLACK);
setColor(y, BLACK);
setColor(parentOf(parentOf(x)), RED);
x = parentOf(parentOf(x));
} else {
if (x == rightOf(parentOf(x))) {
x = parentOf(x);
rotateLeft(x);
}
setColor(parentOf(x), BLACK);
setColor(parentOf(parentOf(x)), RED);
rotateRight(parentOf(parentOf(x)));
}
} else {
Entry<K,V> y = leftOf(parentOf(parentOf(x)));
if (colorOf(y) == RED) {
setColor(parentOf(x), BLACK);
setColor(y, BLACK);
setColor(parentOf(parentOf(x)), RED);
x = parentOf(parentOf(x));
} else {
if (x == leftOf(parentOf(x))) {
x = parentOf(x);
rotateRight(x);
}
setColor(parentOf(x), BLACK);
setColor(parentOf(parentOf(x)), RED);
rotateLeft(parentOf(parentOf(x)));
}
}
}
root.color = BLACK;
}
回到add
添加成功
三、总结
TreeSet的底层是TreeMap
TreeMap采用红黑树的存储方式,红黑树是一种特殊的二叉搜索树
红黑树的性质:
1.每个节点或是红色的,或是黑色的
2.根节点是黑色的
3.每个叶节点(NIL)是黑色的
4.如果一个节点是红色的,则它的两个子节点都是黑色的
5.对于每个几点,从该节点到其所有后代叶节点的简单路径上,均包含相同数目的黑色节点。
TreeMap默认采用自然排序的方式