看这篇博客前,能够先看下下列这几篇博客java
TreeMap 的实现使用了红黑树数据结构,也就是一棵自平衡的排序二叉树,这样就能够保证快速检索指定节点。对于 TreeMap 而言,它采用一种被称为“红黑树”的排序二叉树来保存 Map 中每一个 Entry —— 每一个 Entry 都被当成“红黑树”的一个节点对待。举例:
当程序执行 map.put("ccc" , 89.0); 时,系统将直接把 "ccc"-89.0 这个 Entry 放入 Map 中,这个 Entry 就是该“红黑树”的根节点。接着程序执行 map.put("aaa" , 80.0); 时,程序会将 "aaa"-80.0 做为新节点添加到已有的红黑树中。算法
之后每向 TreeMap 中放入一个 key-value 对,系统都须要将该 Entry 当成一个新节点,添加成已有红黑树中,经过这种方式就可保证 TreeMap 中全部 key 老是由小到大地排列。例如咱们输出上面程序,将看到以下结果(全部 key 由小到大地排列):数组
{aaa=80.0, bbb=89.0, ccc=89.0, zzz=80.0}
TreeMap的添加节点(put()方法)
对于 TreeMap 而言,因为它底层采用一棵“红黑树”来保存集合中的 Entry,这意味这 TreeMap 添加元素、取出元素的性能都比 HashMap 低(红黑树和Hash数据结构上的区别):当 TreeMap 添加元素时,须要经过循环找到新增 Entry 的插入位置,所以比较耗性能;当从 TreeMap 中取出元素时,须要经过循环才能找到合适的 Entry,也比较耗性能。但 TreeMap、TreeSet 比 HashMap、HashSet 的优点在于:TreeMap 中的全部 Entry 老是按 key 根据指定排序规则保持有序状态,TreeSet 中全部元素老是根据指定排序规则保持有序状态。
为了很好的理解TreeMap你必须先理解红黑树,然而红黑树又是一种特殊的二叉查找树,因此你必须先看两篇博客
private void deleteEntry(Entry<K,V> p) { modCount++; size--; // 若是被删除节点的左子树、右子树都不为空 if (p.left != null && p.right != null) { // 用 p 节点的中序后继节点代替 p 节点 Entry<K,V> s = successor (p); p.key = s.key; p.value = s.value; p = s; } // 若是 p 节点的左节点存在,replacement 表明左节点;不然表明右节点。 Entry<K,V> replacement = (p.left != null ? p.left : p.right); if (replacement != null) { replacement.parent = p.parent; // 若是 p 没有父节点,则 replacemment 变成父节点 if (p.parent == null) root = replacement; // 若是 p 节点是其父节点的左子节点 else if (p == p.parent.left) p.parent.left = replacement; // 若是 p 节点是其父节点的右子节点 else p.parent.right = replacement; p.left = p.right = p.parent = null; // 修复红黑树 if (p.color == BLACK) fixAfterDeletion(replacement); // ① } // 若是 p 节点没有父节点 else if (p.parent == null) { root = null; } else { if (p.color == BLACK) // 修复红黑树 fixAfterDeletion(p); // ② if (p.parent != null) { // 若是 p 是其父节点的左子节点 if (p == p.parent.left) p.parent.left = null; // 若是 p 是其父节点的右子节点 else if (p == p.parent.right) p.parent.right = null; p.parent = null; } } }
检索节点数据结构
当 TreeMap 根据 key 来取出 value 时,TreeMap 对应的方法以下:多线程
public V get(Object key) { // 根据指定 key 取出对应的 Entry Entry>K,V< p = getEntry(key); // 返回该 Entry 所包含的 value return (p==null ? null : p.value); }
从上面程序的粗体字代码能够看出,get(Object key) 方法实质是因为 getEntry() 方法实现的,这个 getEntry() 方法的代码以下:ide
final Entry<K,V> getEntry(Object key) { // 若是 comparator 不为 null,代表程序采用定制排序 if (comparator != null) // 调用 getEntryUsingComparator 方法来取出对应的 key return getEntryUsingComparator(key); // 若是 key 形参的值为 null,抛出 NullPointerException 异常 if (key == null) throw new NullPointerException(); // 将 key 强制类型转换为 Comparable 实例 Comparable<? super K> k = (Comparable<? super K>) key; // 从树的根节点开始 Entry<K,V> p = root; while (p != null) { // 拿 key 与当前节点的 key 进行比较 int cmp = k.compareTo(p.key); // 若是 key 小于当前节点的 key,向“左子树”搜索 if (cmp < 0) p = p.left; // 若是 key 大于当前节点的 key,向“右子树”搜索 else if (cmp > 0) p = p.right; // 不大于、不小于,就是找到了目标 Entry else return p; } return null; }
上面的 getEntry(Object obj) 方法也是充分利用排序二叉树的特征来搜索目标 Entry,程序依然从二叉树的根节点开始,若是被搜索节点大于当前节点,程序向“右子树”搜索;若是被搜索节点小于当前节点,程序向“左子树”搜索;若是相等,那就是找到了指定节点。工具
当 TreeMap 里的 comparator != null 即代表该 TreeMap 采用了定制排序,在采用定制排序的方式下,TreeMap 采用 getEntryUsingComparator(key) 方法来根据 key 获取 Entry。下面是该方法的代码:性能
final Entry<K,V> getEntryUsingComparator(Object key) { K k = (K) key; // 获取该 TreeMap 的 comparator Comparator<? super K> cpr = comparator; if (cpr != null) { // 从根节点开始 Entry<K,V> p = root; while (p != null) { // 拿 key 与当前节点的 key 进行比较 int cmp = cpr.compare(k, p.key); // 若是 key 小于当前节点的 key,向“左子树”搜索 if (cmp < 0) p = p.left; // 若是 key 大于当前节点的 key,向“右子树”搜索 else if (cmp > 0) p = p.right; // 不大于、不小于,就是找到了目标 Entry else return p; } } return null; }
其实 getEntry、getEntryUsingComparator 两个方法的实现思路彻底相似,只是前者对天然排序的 TreeMap 获取有效,后者对定制排序的 TreeMap 有效。ui
经过上面源代码的分析不难看出,TreeMap 这个工具类的实现其实很简单。或者说:从内部结构来看,TreeMap 本质上就是一棵“红黑树”,而 TreeMap 的每一个 Entry 就是该红黑树的一个节点。spa
其实这个问题就是在问红黑树相对于排序二叉树的优势。咱们都知道排序二叉树虽然能够快速检索,但在最坏的状况下:若是插入的节点集自己就是有序的,要么是由小到大排列,要么是由大到小排列,那么最后获得的排序二叉树将变成链表:全部节点只有左节点(若是插入节点集自己是大到小排列);或全部节点只有右节点(若是插入节点集自己是小到大排列)。在这种状况下,排序二叉树就变成了普通链表,其检索效率就会不好。
为了改变排序二叉树存在的不足,Rudolf Bayer 与 1972 年发明了另外一种改进后的排序二叉树:红黑树,他将这种排序二叉树称为“对称二叉 B 树”,而红黑树这个名字则由 Leo J. Guibas 和 Robert Sedgewick 于 1978 年首次提出。
红黑树是一个更高效的检索二叉树,所以经常用来实现关联数组。典型地,JDK 提供的集合类 TreeMap 自己就是一个红黑树的实现。
红黑树在原有的排序二叉树增长了以下几个要求:
Java 中实现的红黑树可能有如图 6 所示结构:
备注:本文中全部关于红黑树中的示意图采用白色表明红色。黑色节点仍是采用了黑色表示。
根据性质 5:红黑树从根节点到每一个叶子节点的路径都包含相同数量的黑色节点,所以从根节点到叶子节点的路径中包含的黑色节点数被称为树的“黑色高度(black-height)”。
性质 4 则保证了从根节点到叶子节点的最长路径的长度不会超过任何其余路径的两倍。假若有一棵黑色高度为 3 的红黑树:从根节点到叶节点的最短路径长度是 2,该路径上全是黑色节点(黑节点 - 黑节点 - 黑节点)。最长路径也只可能为 4,在每一个黑色节点之间插入一个红色节点(黑节点 - 红节点 - 黑节点 - 红节点 - 黑节点),性质 4 保证毫不可能插入更多的红色节点。因而可知,红黑树中最长路径就是一条红黑交替的路径。
由此咱们能够得出结论:对于给定的黑色高度为 N 的红黑树,从根到叶子节点的最短路径长度为 N-1,最长路径长度为 2 * (N-1)。
提示:排序二叉树的深度直接影响了检索的性能,正如前面指出,当插入节点自己就是由小到大排列时,排序二叉树将变成一个链表,这种排序二叉树的检索性能最低:N 个节点的二叉树深度就是 N-1。
红黑树经过上面这种限制来保证它大体是平衡的——由于红黑树的高度不会无限增高,这样保证红黑树在最坏状况下都是高效的,不会出现普通排序二叉树的状况。
因为红黑树只是一个特殊的排序二叉树,所以对红黑树上的只读操做与普通排序二叉树上的只读操做彻底相同,只是红黑树保持了大体平衡,所以检索性能比排序二叉树要好不少。
但在红黑树上进行插入操做和删除操做会致使树再也不符合红黑树的特征,所以插入操做和删除操做都须要进行必定的维护,以保证插入节点、删除节点后的树依然是红黑树。
”TreeMap、TreeSet 对比 HashMap、HashSet的优缺点?“
缺点:
对于 TreeMap 而言,因为它底层采用一棵“红黑树”来保存集合中的 Entry,这意味这 TreeMap 添加元素、取出元素的性能都比 HashMap (O(1))低:
TreeMap 中的全部 Entry 老是按 key 根据指定排序规则保持有序状态,TreeSet 中全部元素老是根据指定排序规则保持有序状态。