与HashMap相比,TreeMap是一个能比较元素大小的Map集合,会对传入的key进行了大小排序。其中,可以使用元素的自然顺序,也可以使用集合中自定义的比较器来进行排序;

TreeMap实现了红黑树的结构,形成了一颗二叉树。

构造方法

  1. public TreeMap() {
  2. comparator = null;
  3. }
  4. public TreeMap(Comparator<? super K> comparator) {
  5. this.comparator = comparator;
  6. }
  7. public TreeMap(Map<? extends K, ? extends V> m) {
  8. comparator = null;
  9. putAll(m);
  10. }
  11. public TreeMap(SortedMap<K, ? extends V> m) {
  12. comparator = m.comparator();
  13. try {
  14. buildFromSorted(m.size(), m.entrySet().iterator(), null, null);
  15. } catch (java.io.IOException cannotHappen) {
  16. } catch (ClassNotFoundException cannotHappen) {
  17. }
  18. }

comparator 比较器支持外部传入,默认为null,会直接调用 key 的 compareTo 方法,要求key实现了 Comparable 接口

put(K key, V value)

  1. public V put(K key, V value) {
  2. Entry<K,V> t = root;
  3. /**
  4. * 如果根节点都为null,还没建立起来红黑树,我们先new Entry并赋值给root把红黑树建立起来,这个时候红
  5. * 黑树中已经有一个节点了,同时修改操作+1。
  6. */
  7. if (t == null) {
  8. compare(key, key);
  9. root = new Entry<>(key, value, null);
  10. size = 1;
  11. modCount++;
  12. return null;
  13. }
  14. /**
  15. * 如果节点不为null,定义一个cmp,这个变量用来进行二分查找时的比较;定义parent,是new Entry时必须
  16. * 要的参数
  17. */
  18. int cmp;
  19. Entry<K,V> parent;
  20. // cpr表示有无自己定义的排序规则,分两种情况遍历执行
  21. Comparator<? super K> cpr = comparator;
  22. if (cpr != null) {
  23. /**
  24. * 从root节点开始遍历,通过二分查找逐步向下找
  25. * 第一次循环:从根节点开始,这个时候parent就是根节点,然后通过自定义的排序算法
  26. * cpr.compare(key, t.key)比较传入的key和根节点的key值,如果传入的key<root.key,那么
  27. * 继续在root的左子树中找,从root的左孩子节点(root.left)开始:如果传入的key>root.key,
  28. * 那么继续在root的右子树中找,从root的右孩子节点(root.right)开始;如果恰好key==root.key,
  29. * 那么直接根据root节点的value值即可。
  30. * 后面的循环规则一样,当遍历到的当前节点作为起始节点,逐步往下找
  31. *
  32. * 需要注意的是:这里并没有对key是否为null进行判断,建议自己的实现Comparator时应该要考虑在内
  33. */
  34. do {
  35. parent = t;
  36. cmp = cpr.compare(key, t.key);
  37. if (cmp < 0)
  38. t = t.left;
  39. else if (cmp > 0)
  40. t = t.right;
  41. else
  42. return t.setValue(value);
  43. } while (t != null);
  44. }
  45. else {
  46. //从这里看出,当默认排序时,key值是不能为null的
  47. if (key == null)
  48. throw new NullPointerException();
  49. @SuppressWarnings("unchecked")
  50. Comparable<? super K> k = (Comparable<? super K>) key;
  51. //这里的实现逻辑和上面一样,都是通过二分查找,就不再多说了
  52. do {
  53. parent = t;
  54. cmp = k.compareTo(t.key);
  55. if (cmp < 0)
  56. t = t.left;
  57. else if (cmp > 0)
  58. t = t.right;
  59. else
  60. return t.setValue(value);
  61. } while (t != null);
  62. }
  63. /**
  64. * 能执行到这里,说明前面并没有找到相同的key,节点已经遍历到最后了,我们只需要new一个Entry放到
  65. * parent下面即可,但放到左子节点上还是右子节点上,就需要按照红黑树的规则来。
  66. */
  67. Entry<K,V> e = new Entry<>(key, value, parent);
  68. if (cmp < 0)
  69. parent.left = e;
  70. else
  71. parent.right = e;
  72. /**
  73. * 节点加进去了,并不算完,我们在前面红黑树原理章节提到过,一般情况下加入节点都会对红黑树的结构造成
  74. * 破坏,我们需要通过一些操作来进行自动平衡处置,如【变色】【左旋】【右旋】
  75. */
  76. fixAfterInsertion(e);
  77. size++;
  78. modCount++;
  79. return null;
  80. }

get(Object key)

  1. public V get(Object key) {
  2. Entry<K,V> p = getEntry(key);
  3. return (p==null ? null : p.value);
  4. }
  5. /**
  6. * 从root节点开始遍历,通过二分查找逐步向下找
  7. * 第一次循环:从根节点开始,这个时候parent就是根节点,然后通过k.compareTo(p.key)比较传入的key和
  8. * 根节点的key值;
  9. * 如果传入的key<root.key, 那么继续在root的左子树中找,从root的左孩子节点(root.left)开始;
  10. * 如果传入的key>root.key, 那么继续在root的右子树中找,从root的右孩子节点(root.right)开始;
  11. * 如果恰好key==root.key,那么直接根据root节点的value值即可。
  12. * 后面的循环规则一样,当遍历到的当前节点作为起始节点,逐步往下找
  13. */
  14. //默认排序情况下的查找
  15. final Entry<K,V> getEntry(Object key) {
  16. if (comparator != null)
  17. return getEntryUsingComparator(key);
  18. if (key == null)
  19. throw new NullPointerException();
  20. @SuppressWarnings("unchecked")
  21. Comparable<? super K> k = (Comparable<? super K>) key;
  22. Entry<K,V> p = root;
  23. while (p != null) {
  24. int cmp = k.compareTo(p.key);
  25. if (cmp < 0)
  26. p = p.left;
  27. else if (cmp > 0)
  28. p = p.right;
  29. else
  30. return p;
  31. }
  32. return null;
  33. }
  34. /**
  35. * 从root节点开始遍历,通过二分查找逐步向下找
  36. * 第一次循环:从根节点开始,这个时候parent就是根节点,然后通过自定义的排序算法
  37. * cpr.compare(key, t.key)比较传入的key和根节点的key值,如果传入的key<root.key,那么
  38. * 继续在root的左子树中找,从root的左孩子节点(root.left)开始:如果传入的key>root.key,
  39. * 那么继续在root的右子树中找,从root的右孩子节点(root.right)开始;如果恰好key==root.key,
  40. * 那么直接根据root节点的value值即可。
  41. * 后面的循环规则一样,当遍历到的当前节点作为起始节点,逐步往下找
  42. */
  43. //自定义排序规则下的查找
  44. final Entry<K,V> getEntryUsingComparator(Object key) {
  45. @SuppressWarnings("unchecked")
  46. K k = (K) key;
  47. Comparator<? super K> cpr = comparator;
  48. if (cpr != null) {
  49. Entry<K,V> p = root;
  50. while (p != null) {
  51. int cmp = cpr.compare(k, p.key);
  52. if (cmp < 0)
  53. p = p.left;
  54. else if (cmp > 0)
  55. p = p.right;
  56. else
  57. return p;
  58. }
  59. }
  60. return null;
  61. }

remove(Object key)

remove方法可以分为两个步骤,先是找到这个节点,直接调用了上面介绍的getEntry(Object key),这个步骤我们就不说了,直接说第二个步骤,找到后的删除操作

  1. public V remove(Object key) {
  2. Entry<K,V> p = getEntry(key);
  3. if (p == null)
  4. return null;
  5. V oldValue = p.value;
  6. deleteEntry(p);
  7. return oldValue;
  8. }

通过deleteEntry(p)进行删除操作

  1. 删除的是根节点,则直接将根节点置为null;
  2. 待删除节点的左右子节点都为null,删除时将该节点置为null;
  3. 待删除节点的左右子节点有一个有值,则用有值的节点替换该节点即可;
  4. 待删除节点的左右子节点都不为null,则找前驱或者后继,将前驱或者后继的值复制到该节点中,然后删除前驱或者后继(前驱:左子树中值最大的节点,后继:右子树中值最小的节点)
  1. private void deleteEntry(Entry<K,V> p) {
  2. modCount++;
  3. size--;
  4. //当左右子节点都不为null时,通过successor(p)遍历红黑树找到前驱或者后继
  5. if (p.left != null && p.right != null) {
  6. Entry<K,V> s = successor(p);
  7. //将前驱或者后继的key和value复制到当前节点p中,然后删除节点s(通过将节点p引用指向s)
  8. p.key = s.key;
  9. p.value = s.value;
  10. p = s;
  11. }
  12. Entry<K,V> replacement = (p.left != null ? p.left : p.right);
  13. /**
  14. * 至少有一个子节点不为null,直接用这个有值的节点替换掉当前节点,给replacement的parent属性赋值,给
  15. * parent节点的left属性和right属性赋值,同时要记住叶子节点必须为null,然后用fixAfterDeletion方法
  16. * 进行自平衡处理
  17. */
  18. if (replacement != null) {
  19. //将待删除节点的子节点挂到待删除节点的父节点上。
  20. replacement.parent = p.parent;
  21. if (p.parent == null)
  22. root = replacement;
  23. else if (p == p.parent.left)
  24. p.parent.left = replacement;
  25. else
  26. p.parent.right = replacement;
  27. p.left = p.right = p.parent = null;
  28. /**
  29. * p如果是红色节点的话,那么其子节点replacement必然为红色的,并不影响红黑树的结构
  30. * 但如果p为黑色节点的话,那么其父节点以及子节点都可能是红色的,那么很明显可能会存在红色相连的情
  31. * 况,因此需要进行自平衡的调整
  32. */
  33. if (p.color == BLACK)
  34. fixAfterDeletion(replacement);
  35. } else if (p.parent == null) {//这种情况就不用多说了吧
  36. root = null;
  37. } else {
  38. /**
  39. * 如果p节点为黑色,那么p节点删除后,就可能违背每个节点到其叶子节点路径上黑色节点数量一致的规则,
  40. * 因此需要进行自平衡的调整
  41. */
  42. if (p.color == BLACK)
  43. fixAfterDeletion(p);
  44. if (p.parent != null) {
  45. if (p == p.parent.left)
  46. p.parent.left = null;
  47. else if (p == p.parent.right)
  48. p.parent.right = null;
  49. p.parent = null;
  50. }
  51. }
  52. }

红黑树部分就不 copy 了…

参考 TreeMap原理实现及常用方法