简介
TreeMap使用红黑树存储元素,可以保证元素按key值的大小进行遍历。
实战另见 TreeMap
继承体系
TreeMap实现了Map、SortedMap、NavigableMap、Cloneable、Serializable等接口。
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); }
|
存储结构
TreeMap只使用到了红黑树,所以它的时间复杂度为O(log n),我们再来回顾一下红黑树的特性。
- 每个节点或者是黑色,或者是红色。
- 根节点是黑色。
- 每个叶子节点(NIL)是黑色。(注意:这里叶子节点,是指为空(NIL或NULL)的叶子节点!)
- 如果一个节点是红色的,则它的子节点必须是黑色的。
- 从一个节点到该节点的子孙节点的所有路径上包含相同数目的黑节点。
源码解析
属性
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;
|
(1)comparator
按key的大小排序有两种方式,一种是key实现Comparable接口,一种方式通过构造方法传入比较器。
(2)root
根节点,TreeMap没有桶的概念,所有的元素都存储在一颗树中。
Entry内部类
存储节点,典型的红黑树结构。
1 2 3 4 5 6 7 8
| 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; }
|
构造方法
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
|
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比较器,一类是key必须实现Comparable接口。
其实,笔者认为这两种比较方式可以合并成一种,当没有传comparator的时候,可以用以下方式来给comparator赋值,这样后续所有的比较操作都可以使用一样的逻辑处理了,而不用每次都检查comparator为空的时候又用Comparable来实现一遍逻辑。
1 2 3
|
comparator = (k1, k2) -> ((Comparable<? super K>)k1).compareTo(k2);
|
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
| 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; }
|
- 从root遍历整个树;
- 如果待查找的key比当前遍历的key小,则在其左子树中查找;
- 如果待查找的key比当前遍历的key大,则在其右子树中查找;
- 如果待查找的key与当前遍历的key相等,则找到了该元素,直接返回;
- 从这里可以看出是否有comparator分化成了两个方法,但是内部逻辑一模一样,因此可见笔者
comparator = (k1, k2) -> ((Comparable<? super K>)k1).compareTo(k2);
这种改造的必要性。
我是一条美丽的分割线,前方高能,请做好准备。
特性再回顾
- 每个节点或者是黑色,或者是红色。
- 根节点是黑色。
- 每个叶子节点(NIL)是黑色。(注意:这里叶子节点,是指为空(NIL或NULL)的叶子节点!)
- 如果一个节点是红色的,则它的子节点必须是黑色的。
- 从一个节点到该节点的子孙节点的所有路径上包含相同数目的黑节点。
左旋
左旋,就是以某个节点为支点向左旋转。
整个左旋过程如下:
- 将 y的左节点 设为 x的右节点,即将 β 设为 x的右节点;
- 将 x 设为 y的左节点的父节点,即将 β的父节点 设为 x;
- 将 x的父节点 设为 y的父节点;
- 如果 x的父节点 为空节点,则将y设置为根节点;如果x是它父节点的左(右)节点,则将y设置为x父节点的左(右)节点;
- 将 x 设为 y的左节点;
- 将 x的父节点 设为 y;
让我们来看看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 25 26 27 28 29 30 31 32 33 34 35 36 37
|
private void rotateLeft(Entry<K,V> p) { if (p != null) { Entry<K,V> r = p.right; p.right = r.left; if (r.left != null) r.left.parent = p;
r.parent = p.parent;
if (p.parent == null) root = r; else if (p.parent.left == p) p.parent.left = r; else p.parent.right = r;
r.left = p;
p.parent = r; } }
|
右旋
右旋,就是以某个节点为支点向右旋转。
整个右旋过程如下:
- 将 x的右节点 设为 y的左节点,即 将 β 设为 y的左节点;
- 将 y 设为 x的右节点的父节点,即 将 β的父节点 设为 y;
- 将 y的父节点 设为 x的父节点;
- 如果 y的父节点 是 空节点,则将x设为根节点;如果y是它父节点的左(右)节点,则将x设为y的父节点的左(右)节点;
- 将 y 设为 x的右节点;
- 将 y的父节点 设为 x;
让我们来看看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 25 26 27 28 29 30 31 32 33 34 35 36
|
private void rotateRight(Entry<K,V> p) { if (p != null) { Entry<K,V> l = p.left;
p.left = l.right;
if (l.right != null) l.right.parent = p;
l.parent = p.parent;
if (p.parent == null) root = l; else if (p.parent.right == p) p.parent.right = l; else p.parent.left = l;
l.right = p;
p.parent = l; } }
|