1. 概述
HashMap不保证数据有序,LinkedHashMap保证数据可以保持插入顺序,而如果我们希望Map可以保持key的大小顺序的时候,我们就需要利用TreeMap了。
1 2 3 4 5 6 7 8 9 10 11 12 | TreeMap<Integer, String> tmap = new TreeMap<Integer, String>(); tmap.put( 1 , "语文" ); tmap.put( 3 , "英语" ); tmap.put( 2 , "数学" ); tmap.put( 4 , "政治" ); tmap.put( 5 , "历史" ); tmap.put( 6 , "地理" ); tmap.put( 7 , "生物" ); tmap.put( 8 , "化学" ); for (Entry<Integer, String> entry : tmap.entrySet()) { System.out.println(entry.getKey() + ": " + entry.getValue()); } |
其大致的结构如下所示:
使用红黑树的好处是能够使得树具有不错的平衡性,这样操作的速度就可以达到log(n)的水平了。具体红黑树的实现不在这里赘述。
2. put函数
Associates the specified value with the specified key in this map.If the map previously contained a mapping for the key, the old value is replaced.
如果存在的话,old value被替换;如果不存在的话,则新添一个节点,然后对做红黑树的平衡操作。
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 | public V put(K key, V value) { Entry<K,V> t = root; if (t == null ) { 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 ; } |
3. get函数
get函数则相对来说比较简单,以log(n)的复杂度进行get
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 | final Entry<K,V> getEntry(Object key) { // Offload comparator-based version for sake of performance 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 ; } public V get(Object key) { Entry<K,V> p = getEntry(key); return (p== null ? null : p.value); } |
4. successor后继
TreeMap是如何保证其迭代输出是有序的呢?其实从宏观上来讲,就相当于树的中序遍历(LDR)。我们先看一下迭代输出的步骤
1 2 3 | for (Entry<Integer, String> entry : tmap.entrySet()) { System.out.println(entry.getKey() + ": " + entry.getValue()); } |
根据The enhanced for statement,for语句会做如下转换为:
1 2 3 4 | for (Iterator<Map.Entry<String, String>> it = tmap.entrySet().iterator() ; tmap.hasNext(); ) { Entry<Integer, String> entry = it.next(); System.out.println(entry.getKey() + ": " + entry.getValue()); } |
在it.next()的调用中会使用nextEntry调用successor
这个是过的后继的重点,具体实现如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | 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 { // 如果右子树为空,则寻找当前节点所在左子树的第一个祖先节点 // 因为左子树找完了,根据LDR该D了 Entry<K,V> p = t.parent; Entry<K,V> ch = t; // 保证左子树 while (p != null && ch == p.right) { ch = p; p = p.parent; } return p; } } |
怎么理解这个successor呢?只要记住,这个是中序遍历就好了,L-D-R。具体细节如下:
a. 空节点,没有后继
b. 有右子树的节点,后继就是右子树的“最左节点”
c. 无右子树的节点,后继就是该节点所在左子树的第一个祖先节点
a.好理解,不过b, c,有点像绕口令啊,没关系,上图举个例子就懂了!
有右子树的节点,节点的下一个节点,肯定在右子树中,而右子树中“最左”的那个节点则是右子树中最小的一个,那么当然是右子树的“最左节点”,就好像下图所示:
无右子树的节点,先找到这个节点所在的左子树(右图),那么这个节点所在的左子树的父节点(绿色节点),就是下一个节点。