TreeSet和TreeMap
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默认采用自然排序的方式