与HashMap相比,TreeMap是一个能比较元素大小的Map集合,会对传入的key进行了大小排序。其中,可以使用元素的自然顺序,也可以使用集合中自定义的比较器来进行排序;
构造方法
public TreeMap() {comparator = null;}public TreeMap(Comparator<? super K> comparator) {this.comparator = comparator;}public TreeMap(Map<? extends K, ? extends V> m) {comparator = null;putAll(m);}public TreeMap(SortedMap<K, ? extends V> m) {comparator = m.comparator();try {buildFromSorted(m.size(), m.entrySet().iterator(), null, null);} catch (java.io.IOException cannotHappen) {} catch (ClassNotFoundException cannotHappen) {}}
comparator 比较器支持外部传入,默认为null,会直接调用 key 的 compareTo 方法,要求key实现了 Comparable 接口
put(K key, V value)
public V put(K key, V value) {Entry<K,V> t = root;/*** 如果根节点都为null,还没建立起来红黑树,我们先new Entry并赋值给root把红黑树建立起来,这个时候红* 黑树中已经有一个节点了,同时修改操作+1。*/if (t == null) {compare(key, key);root = new Entry<>(key, value, null);size = 1;modCount++;return null;}/*** 如果节点不为null,定义一个cmp,这个变量用来进行二分查找时的比较;定义parent,是new Entry时必须* 要的参数*/int cmp;Entry<K,V> parent;// cpr表示有无自己定义的排序规则,分两种情况遍历执行Comparator<? super K> cpr = comparator;if (cpr != null) {/*** 从root节点开始遍历,通过二分查找逐步向下找* 第一次循环:从根节点开始,这个时候parent就是根节点,然后通过自定义的排序算法* cpr.compare(key, t.key)比较传入的key和根节点的key值,如果传入的key<root.key,那么* 继续在root的左子树中找,从root的左孩子节点(root.left)开始:如果传入的key>root.key,* 那么继续在root的右子树中找,从root的右孩子节点(root.right)开始;如果恰好key==root.key,* 那么直接根据root节点的value值即可。* 后面的循环规则一样,当遍历到的当前节点作为起始节点,逐步往下找** 需要注意的是:这里并没有对key是否为null进行判断,建议自己的实现Comparator时应该要考虑在内*/do {parent = t;cmp = cpr.compare(key, t.key);if (cmp < 0)t = t.left;else if (cmp > 0)t = t.right;elsereturn t.setValue(value);} while (t != null);}else {//从这里看出,当默认排序时,key值是不能为null的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;elsereturn t.setValue(value);} while (t != null);}/*** 能执行到这里,说明前面并没有找到相同的key,节点已经遍历到最后了,我们只需要new一个Entry放到* parent下面即可,但放到左子节点上还是右子节点上,就需要按照红黑树的规则来。*/Entry<K,V> e = new Entry<>(key, value, parent);if (cmp < 0)parent.left = e;elseparent.right = e;/*** 节点加进去了,并不算完,我们在前面红黑树原理章节提到过,一般情况下加入节点都会对红黑树的结构造成* 破坏,我们需要通过一些操作来进行自动平衡处置,如【变色】【左旋】【右旋】*/fixAfterInsertion(e);size++;modCount++;return null;}
get(Object key)
public V get(Object key) {Entry<K,V> p = getEntry(key);return (p==null ? null : p.value);}/*** 从root节点开始遍历,通过二分查找逐步向下找* 第一次循环:从根节点开始,这个时候parent就是根节点,然后通过k.compareTo(p.key)比较传入的key和* 根节点的key值;* 如果传入的key<root.key, 那么继续在root的左子树中找,从root的左孩子节点(root.left)开始;* 如果传入的key>root.key, 那么继续在root的右子树中找,从root的右孩子节点(root.right)开始;* 如果恰好key==root.key,那么直接根据root节点的value值即可。* 后面的循环规则一样,当遍历到的当前节点作为起始节点,逐步往下找*///默认排序情况下的查找final Entry<K,V> getEntry(Object key) {if (comparator != null)return getEntryUsingComparator(key);if (key == null)throw new NullPointerException();@SuppressWarnings("unchecked")Comparable<? super K> k = (Comparable<? super K>) key;Entry<K,V> p = root;while (p != null) {int cmp = k.compareTo(p.key);if (cmp < 0)p = p.left;else if (cmp > 0)p = p.right;elsereturn p;}return null;}/*** 从root节点开始遍历,通过二分查找逐步向下找* 第一次循环:从根节点开始,这个时候parent就是根节点,然后通过自定义的排序算法* cpr.compare(key, t.key)比较传入的key和根节点的key值,如果传入的key<root.key,那么* 继续在root的左子树中找,从root的左孩子节点(root.left)开始:如果传入的key>root.key,* 那么继续在root的右子树中找,从root的右孩子节点(root.right)开始;如果恰好key==root.key,* 那么直接根据root节点的value值即可。* 后面的循环规则一样,当遍历到的当前节点作为起始节点,逐步往下找*///自定义排序规则下的查找final Entry<K,V> getEntryUsingComparator(Object key) {@SuppressWarnings("unchecked")K k = (K) key;Comparator<? super K> cpr = comparator;if (cpr != null) {Entry<K,V> p = root;while (p != null) {int cmp = cpr.compare(k, p.key);if (cmp < 0)p = p.left;else if (cmp > 0)p = p.right;elsereturn p;}}return null;}
remove(Object key)
remove方法可以分为两个步骤,先是找到这个节点,直接调用了上面介绍的getEntry(Object key),这个步骤我们就不说了,直接说第二个步骤,找到后的删除操作
public V remove(Object key) {Entry<K,V> p = getEntry(key);if (p == null)return null;V oldValue = p.value;deleteEntry(p);return oldValue;}
通过deleteEntry(p)进行删除操作
- 删除的是根节点,则直接将根节点置为null;
- 待删除节点的左右子节点都为null,删除时将该节点置为null;
- 待删除节点的左右子节点有一个有值,则用有值的节点替换该节点即可;
- 待删除节点的左右子节点都不为null,则找前驱或者后继,将前驱或者后继的值复制到该节点中,然后删除前驱或者后继(前驱:左子树中值最大的节点,后继:右子树中值最小的节点)
private void deleteEntry(Entry<K,V> p) {modCount++;size--;//当左右子节点都不为null时,通过successor(p)遍历红黑树找到前驱或者后继if (p.left != null && p.right != null) {Entry<K,V> s = successor(p);//将前驱或者后继的key和value复制到当前节点p中,然后删除节点s(通过将节点p引用指向s)p.key = s.key;p.value = s.value;p = s;}Entry<K,V> replacement = (p.left != null ? p.left : p.right);/*** 至少有一个子节点不为null,直接用这个有值的节点替换掉当前节点,给replacement的parent属性赋值,给* parent节点的left属性和right属性赋值,同时要记住叶子节点必须为null,然后用fixAfterDeletion方法* 进行自平衡处理*/if (replacement != null) {//将待删除节点的子节点挂到待删除节点的父节点上。replacement.parent = p.parent;if (p.parent == null)root = replacement;else if (p == p.parent.left)p.parent.left = replacement;elsep.parent.right = replacement;p.left = p.right = p.parent = null;/*** p如果是红色节点的话,那么其子节点replacement必然为红色的,并不影响红黑树的结构* 但如果p为黑色节点的话,那么其父节点以及子节点都可能是红色的,那么很明显可能会存在红色相连的情* 况,因此需要进行自平衡的调整*/if (p.color == BLACK)fixAfterDeletion(replacement);} else if (p.parent == null) {//这种情况就不用多说了吧root = null;} else {/*** 如果p节点为黑色,那么p节点删除后,就可能违背每个节点到其叶子节点路径上黑色节点数量一致的规则,* 因此需要进行自平衡的调整*/if (p.color == BLACK)fixAfterDeletion(p);if (p.parent != null) {if (p == p.parent.left)p.parent.left = null;else if (p == p.parent.right)p.parent.right = null;p.parent = null;}}}
红黑树部分就不 copy 了…
