1、TreeMap 简介 TreeMap 使用红黑树存储元素,可以保证元素按key值的大小进行遍历。TreeMap底层是基于红黑树 (Red-Black tree)实现,所以在学习TreeMap之前我们我们有必要先了解一下红黑树。
由于 TreeMap 底层采用一棵“红黑树”来保存集合中的 Entry,这意味 TreeMap 添加元素、取出元素的效率都比 HashMap 低 : 🌂当向 TreeMap 添加元素时,需要通过循环找到新增 Entry 的插入位置。 🌂当从 TreeMap 中取出元素时,需要通过循环才能找到合适的 Entry。
但 TreeMap、TreeSet 相较于 HashMap、HashSet 的优势 在于: TreeMap 中的所有 Entry 总是按 key 根据指定排序规则保持有序状态,TreeSet 中所有元素总是根据指定排序规则保持有序状态。
1.1、红黑树(Red Black Tree)简述 红黑树 是一种自平衡二叉查找树 ,是在计算机科学中用到的一种数据结构,典型的用途是实现关联数组。它是在1972年由鲁道夫·贝尔发明的,他称之为“对称二叉B树”,它现代的名字是在 Leo J. Guibas 和 Robert Sedgewick 于1978年写的一篇论文中获得的。它是复杂的,但它的操作有着良好的最坏情况运行时间,并且在实践中是高效的:它可以在O(log n)时间内做查找,插入和删除,这里的n是树中元素的数目。
红黑树中每个节点的值,都大于或等于 在它的左 子树中的所有节点的值,并且小于或等于 在它的右 子树中的所有节点的值,这确保红黑树运行时可以快速地在树中查找和定位的所需节点。
1.1.1、二叉查找树(BST) 为了更好的理解 红黑树,必须先理解二叉查找树 (BST)。其中红黑树又是一种特殊的排序二叉树 。
二叉查找树(BST)是一种特殊结构的二叉树,可以非常方便地对树中所有节点进行排序 和检索 。
1.1.1.1、二叉查找树性质 二叉查找树(BST)要么是一棵空二叉树,要么是具有下列性质 的二叉树:
🌂若它的左子树不空,则左子树上所有节点的值均小于它的根节点的值; 🌂若它的右子树不空,则右子树上所有节点的值均大于它的根节点的值; 🌂它的左、右子树也分别为排序二叉树。
对排序二叉树,若按中序遍历 就可以得到由小到大的有序序列:
1 {2 ,3 ,4 ,8 ,9 ,9 ,10 ,13 ,18 ,21 }
创建排序二叉树的步骤,也就是不断地向排序二叉树添加节点的过程,向排序二叉树添加节点的步骤 如下:
🌂以根节点当前节点开始搜索。 🌂新节点的值和当前节点的值比较。 🌂如果新节点的值更大,则以当前节点的右子节点作为新的当前节点;如果新节点的值更小,则以当前节点的左子节点作为新的当前节点。 🌂重复 2、3 两个步骤,直到搜索到合适的叶子节点为止。 🌂将新节点添加为第 4 步找到的叶子节点的子节点;如果新节点更大,则添加为右子节点;否则添加为左子节点。
1.1.2、红黑树(Red Black Tree) 红黑树(Red Black Tree)是一种自平衡的二叉查找树。除了符合二叉查找树的的特性外,它还具有以下特性 。
1.1.2.1、红黑树性质
🌂节点是红色或黑色。 🌂根节点是黑色。 🌂每个叶子节点都是黑色的空节点(NIL节点)。 🌂每个红色节点的两个子节点都是黑色。(从每个叶子到根的所有路径上不能有两个连续的红色节点) 🌂从任一节点到其每个叶子的所有路径都包含相同数目的黑色节点。
2、TreeMap 源码分析 2.1、TreeMap 的定义 TreeMap 基于红黑树(Red-Black tree)实现。该映射根据其键的自然顺序 进行排序,或者根据创建映射时提供的 Comparator 进行排序,具体取决于使用的构造方法。
TreeMap 的基本操作 containsKey 、get 、put 和 remove 的时间复杂度 是 log(n) 。
TreeMap 是非同步 的。 它的iterator 方法返回的迭代器是fail-fast的。
1 2 3 public class TreeMap <K,V> extends AbstractMap <K,V> implements NavigableMap <K,V>, Cloneable, java.io.Serializable
🌂TreeMap 继承 于AbstractMap,所以它是一个Map,即一个key-value集合。 🌂TreeMap 实现 了NavigableMap接口,意味着它支持一系列的导航方法。比如返回有序的key集合。 🌂TreeMap** 实现了Cloneable接口,意味着它能被克隆。 🌂TreeMap 实现 了java.io.Serializable接口,意味着它支持序列化。 🌂NavigableMap 又接口 继承**了 SortedMap 接口,SortedMap 接口规定了元素可以按key的大小来遍历,并且定义了一些返回部分map的方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 public interface SortedMap <K,V> extends Map <K,V> { Comparator<? super K> comparator(); SortedMap<K,V> subMap (K fromKey, K toKey) ; SortedMap<K,V> headMap (K toKey) ; SortedMap<K,V> tailMap (K fromKey) ; K firstKey () ; K lastKey () ; Set<K> keySet () ; Collection<V> values () ; Set<Map.Entry<K, V>> entrySet(); }
NavigableMap 是对 SortedMap 的增强,定义了一些返回离目标key最近 的元素的方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 public interface NavigableMap <K,V> extends SortedMap <K,V> { Map.Entry<K,V> lowerEntry (K key) ; K lowerKey (K key) ; Map.Entry<K,V> floorEntry (K key) ; K floorKey (K key) ; Map.Entry<K,V> ceilingEntry (K key) ; K ceilingKey (K key) ; Map.Entry<K,V> higherEntry (K key) ; K higherKey (K key) ; Map.Entry<K,V> firstEntry () ; Map.Entry<K,V> lastEntry () ; Map.Entry<K,V> pollFirstEntry () ; Map.Entry<K,V> pollLastEntry () ; NavigableMap<K,V> descendingMap () ; NavigableSet<K> navigableKeySet () ; NavigableSet<K> descendingKeySet () ; NavigableMap<K,V> subMap (K fromKey, boolean fromInclusive, K toKey, boolean toInclusive) ; NavigableMap<K,V> headMap (K toKey, boolean inclusive) ; NavigableMap<K,V> tailMap (K fromKey, boolean inclusive) ; SortedMap<K,V> subMap (K fromKey, K toKey) ; SortedMap<K,V> headMap (K toKey) ; SortedMap<K,V> tailMap (K fromKey) ; }
2.2、TreeMap 属性 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 private final Comparator<? super K> comparator;private transient Entry<K,V> root;private transient int size = 0 ;private transient int modCount = 0 ;
TreeMap的本质是R-B Tree(红黑树),它包含几个重要的成员变量: root, size, comparator。
🌂root 是红黑数的根节点。它是Entry类型,Entry是红黑数的节点,它包含了红黑数的6个基本组成成分:key(键)、value(值)、left(左孩子)、right(右孩子)、parent(父节点)、color(颜色)。Entry节点根据key进行排序,Entry节点包含的内容为value。 🌂红黑数排序时,根据Entry中的key进行排序; 🌂Entry 中的key比较大小是根据比较器comparator来进行判断的。 🌂size是红黑数中节点的个数。
Entry 类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 private static final boolean RED = false ; private static final boolean BLACK = true ; 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; Entry(K key, V value, Entry<K,V> parent) { this .key = key; this .value = value; this .parent = parent; } public K getKey () { return key; } public V getValue () { return value; } public V setValue (V value) { V oldValue = this .value; this .value = value; return oldValue; } public boolean equals (Object o) { if (!(o instanceof Map.Entry)) return false ; Map.Entry<?,?> e = (Map.Entry<?,?>)o; return valEquals(key,e.getKey()) && valEquals(value,e.getValue()); } public int hashCode () { int keyHash = (key==null ? 0 : key.hashCode()); int valueHash = (value==null ? 0 : value.hashCode()); return keyHash ^ valueHash; } public String toString () { return key + "=" + value; } }
Entry 类主要定义了树的节点和父节点引用,和红黑颜色属性,并对equals和hashCode进行重写,以利于比较是否相等 。
2.3、TreeMap 构造函数 2.3.1、TreeMap()
1 2 3 4 5 6 7 8 public TreeMap () { comparator = null ; }
2.3.2、TreeMap(Comparator<? super K> comparator) 1 2 3 4 5 6 public TreeMap (Comparator<? super K> comparator) { this .comparator = comparator; }
2.3.3、TreeMap(Map<? extends K,? extends V> m) 1 2 3 4 5 6 7 8 9 public TreeMap (Map<? extends K, ? extends V> m) { comparator = null ; putAll(m); }
该构造方法调用putAll方法将Map中的所有元素加入到TreeMap中
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 public void putAll (Map<? extends K, ? extends V> map) { int mapSize = map.size(); if (size==0 && mapSize!=0 && map instanceof SortedMap) { Comparator<?> c = ((SortedMap<?,?>)map).comparator(); if (c == comparator || (c != null && c.equals(comparator))) { ++modCount; try { buildFromSorted(mapSize, map.entrySet().iterator(), null , null ); } catch (java.io.IOException cannotHappen) { } catch (ClassNotFoundException cannotHappen) { } return ; } } super .putAll(map); }
如果Map里的元素是有序 的,就调用 buildFromSorted 方法来拷贝Map中的元素,如果Map中的元素不是有序 的,就调用**AbstractMap的putAll(map)**方法,该方法源码如下:
1 2 3 4 public void putAll (Map<? extends K, ? extends V> m) { for (Map.Entry<? extends K , ? extends V > e : m.entrySet()) put(e.getKey(), e.getValue()); }
AbstractMap的putAll(map)方法,是将Map中的元素一个个put(插入 )到TreeMap中的,主要因为Map 中的元素是无序 的,因此要一个个插入到红黑树中,使其有序存放 ,并满足红黑树的性质 。
2.3.4、TreeMap(SortedMap<K,? extends V> m) 1 2 3 4 5 6 7 8 9 10 11 12 13 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) { } }
首先获取给定的SortedMap的比较器,然后调用 buildFromSorted 方法,将 SortedMap 中的元素插入到TreeMap中。由于SortedMap中的元素是有序的,将SortedMap中的元素直接添加到TreeMap中即可。
2.4、put(K key, V value) 插入元素 TreeMap 添加元素实际上是向红黑树中添加节点,插入到指定位置后,再做调整,使其保持红黑树的特性。TreeMap 中使用 Entry 内部类代表节点,TreeMap 集合的 put(K key, V value) 方法实现了将 Entry 放入排序二叉树中,下面我们看一下源码的实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 public V put (K key, V value) { Entry<K,V> t = root; if (t == null ) { compare(key, key); root = new Entry <>(key, value, null ); size = 1 ; modCount++; return null ; } int cmp; Entry<K,V> parent; 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 ; }
每当添加新节点时,总是从树的根节点开始比较,将根节点当成当前节点:
🌂如果新增节点大于当前节点、并且当前节点的右子节点存在,则以右子节点作为当前节点; 🌂如果新增节点小于当前节点、并且当前节点的左子节点存在,则以左子节点作为当前节点; 🌂如果新增节点等于当前节点,则用新增节点覆盖当前节点,并结束循环 🌂重复以上几点,直到找到某个节点的左、右子节点不存在,将新节点添加该节点的子节点 🌂如果新节点比该节点大,则添加为右子节点;如果新节点比该节点小,则添加为左子节点。
2.4.1、插入修正操作 红黑树执行插入操作之后,要执行“插入修正操作 ”。 目的是:确保红黑树在插入节点之后,仍然是一颗红黑树,fixAfterInsertion 是新节点插入后对树进行调整的方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 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; }
2.5、remove(Object key) 删除元素 删除节点的时候调用的是deleteEntry(Entry<K,V> p)方法,这个方法主要是删除 节点并且平衡 红黑树
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 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 方法只需按照二叉排序树的操作步骤实现即可,删除指定节点后,再对树进行调整。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 private void deleteEntry (Entry<K,V> p) { modCount++; size--; if (p.left != null && p.right != null ) { Entry<K,V> s = successor(p); p.key = s.key; p.value = s.value; p = s; } Entry<K,V> replacement = (p.left != null ? p.left : p.right); if (replacement != null ) { replacement.parent = p.parent; if (p.parent == null ) root = replacement; else if (p == p.parent.left) p.parent.left = replacement; else p.parent.right = replacement; p.left = p.right = p.parent = null ; if (p.color == BLACK) fixAfterDeletion(replacement); } else if (p.parent == null ) { root = null ; } else { 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 ; } } }
2.5.1、successor 获取后继节点 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 static <K,V> TreeMap.Entry<K,V> successor (Entry<K,V> t) { if (t == null ) return null ; else if (t.right != null ) { Entry<K,V> p = t.right; while (p.left != null ) p = p.left; return p; } else { Entry<K,V> p = t.parent; Entry<K,V> ch = t; while (p != null && ch == p.right) { ch = p; p = p.parent; } return p; } }
当从二叉搜索树中删除一个节点之后,为了让它依然保持为二叉搜索树,程序必须对该二叉搜索树进行维护。维护可分为如下几种情况:
🌂被删除的节点是叶子节点,则只需将它从其父节点中删除即可。
🌂被删除节点 p 只有左子树,将 p 的左子树 pL 添加成 p 的父节点的左子树即可;被删除节点 p 只有右子树,将 p 的右子树 pR 添加成 p 的父节点的右子树即可。
🌂若被删除节点 p 的左、右子树均非空,有两种做法:
1.将 pL 设为 p 的父节点 q 的左或右子节点(取决于 p 是其父节点 q 的左、右子节点),将 pR 设为 p 节点的中序前趋节点 s 的右子节点(s 是 pL 最右下的节点,也就是 pL 子树中最大的节点)。
2.以 p 节点的中序前趋或后继替代 p 所指节点,然后再从原排序二叉树中删去中序前趋或后继节点即可。(也就是用大于 p 的最小节点或小于 p 的最大节点代替 p 节点即可)。
2.5.2、删除后修正操作 红黑树的删除遇到的主要问题就是被删除路径上的黑色节点减少,于是需要进行一系列旋转和着色
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 private void fixAfterDeletion (Entry<K,V> x) { while (x != root && colorOf(x) == BLACK) { if (x == leftOf(parentOf(x))) { Entry<K,V> sib = rightOf(parentOf(x)); if (colorOf(sib) == RED) { setColor(sib, BLACK); setColor(parentOf (x), RED); rotateLeft( parentOf(x)); sib = rightOf(parentOf (x)); } if (colorOf(leftOf(sib)) == BLACK && colorOf(rightOf (sib)) == BLACK) { setColor(sib, RED); x = parentOf(x); } else { if (colorOf(rightOf(sib)) == BLACK) { setColor(leftOf (sib), BLACK); setColor(sib, RED); rotateRight(sib); sib = rightOf(parentOf (x)); } setColor(sib, colorOf (parentOf(x))); setColor(parentOf (x), BLACK); setColor(rightOf (sib), BLACK); rotateLeft( parentOf(x)); x = root; } } else { Entry<K,V> sib = leftOf(parentOf(x)); if (colorOf(sib) == RED) { setColor(sib, BLACK); setColor(parentOf(x), RED); rotateRight(parentOf(x)); sib = leftOf(parentOf(x)); } if (colorOf(rightOf(sib)) == BLACK && colorOf(leftOf(sib)) == BLACK) { setColor(sib, RED); x = parentOf(x); } else { if (colorOf(leftOf(sib)) == BLACK) { setColor(rightOf(sib), BLACK); setColor(sib, RED); rotateLeft(sib); sib = leftOf(parentOf(x)); } setColor(sib, colorOf(parentOf(x))); setColor(parentOf(x), BLACK); setColor(leftOf(sib), BLACK); rotateRight(parentOf(x)); x = root; } } } setColor(x, BLACK); }
2.6、get(Object key) 获取元素 相对于新增和删除元素来说,获取元素是如此的简单,按照二叉树的遍历查找即可。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 public V get (Object key) { Entry<K,V> p = getEntry(key); return (p==null ? null : p.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; else return p; } return null ; } 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; else return p; } } return null ; }