Map的单元是对键值对的处理,之前分析过的两种Map,HashMap和LinkedHashMap都是用哈希值去寻找我们想要的键值对,优点是由O(1)的查找速度。

那如果我们在一个对查找性能要求不那么高,反而对有序性要求比较高的应用场景呢?

这个时候HashMap就不再适用了,我们需要一种新的Map,在JDK中提供了一个接口:SortedMap,我想分析一下具体的实现中的一种:TreeMap.

HahMap是Key无序的,而TreeMap是Key有序的。

1.看一下基本成员:

public class TreeMap<K,V>
extends AbstractMap<K,V>
implements NavigableMap<K,V>, Cloneable, java.io.Serializable
{
private final Comparator<? super K> comparator;
private transient Entry<K,V> root = null;
private transient int size = 0;
private transient int modCount = 0;
public TreeMap() {
comparator = null;
}
public TreeMap(Comparator<? super K> comparator) {
this.comparator = comparator;
}
//后面省略
}

TreeMap继承了NavigableMap,而NavigableMap继承自SortedMap,为SortedMap添加了搜索选项,NavigableMap有几种方法,分别是不同的比较要求:floorKey是小于等于,ceilingKey是大于等于,lowerKey是小于,higherKey是大于。

注意初始化的时候,有一个Comparator成员,这是用于维持有序的比较器,当我们想做一个自定义数据结构的TreeMap时,可以重写这个比较器。

2.我们看一下Entry的成员:

static final class Entry<K,V> implements Map.Entry<K,V> {
K key;
V value;
Entry<K,V> left = null;
Entry<K,V> right = null;
Entry<K,V> parent;
boolean color = BLACK;
//后续省略
}

咦?木有了熟悉了哈希值,多了left,right,parent,这是我们的树结构,最后看到color,明白了:TreeMap是基于红黑树实现的!而且默认的节点颜色是黑色。

至于红黑树,想必多多少少都听过,这是一种平衡的二叉查找树,是2-3树的一种变体,即拥有二叉查找树的高效查找,拥有2-3树的高效平衡插入能力。

红黑树巧妙的增加了颜色这个维度,对2-3树的树本身进行了降维成了二叉树,这样树的调整不会再如2-3树那么繁琐。

有的同学看到这里会质疑我,你这个胡说八道,和算法导论里讲的不一样!

对,CLRS中确实没有这段,这段选自《Algorithms》,我觉得提供了一种有趣的理解思路,所以如果之前只看了CLRS,建议去看一下这本书,互相验证。

不过为了尊重JDK的作者,后面的还是按照CLRS中的讲解来吧,毕竟在JDK源码的注释中写着:From CLR。

我们在红黑树中的一切插入和删除后,为了维护树的有序性的动作看起来繁复,但都是为了维护下面几个红黑树的基本性质:

(1)树的节点只有红与黑两种颜色
(2)根节点为黑色的
(3)叶子节点为黑色的
(4)红色节点的字节点必定是黑色的
(5)从任意一节点出发,到其后继的叶子节点的路径中,黑色节点的数目相同

红黑树的第4条性质保证了这些路径中的任意一条都不存在连续的红节点,而红黑树的第5条性质又保证了所有的这些路径上的黑色节点的数目相同。因而最短路径必定是只包含黑色节点的路径,而最长路径为红黑节点互相交叉的路径,由于所有的路径的起点必须是黑色的,而红色节点又不能连续存在,因而最长路径的长度为全为黑色节点路径长度的二倍。

回到TreeMap本身,看看它的put方法:

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();
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;
}

此处就是二叉树的比较查找到合适的位置,然后插入,需要注意的是

(1)先检测root节点是不是null,如果为null,则新插入的节点为root节点。

(2)最好自定义自己的Comparator,否则将会继承原始的比较方法,可能会出现问题

(3)插入的键值不能为null,否则会抛出空指针的异常。

(4)插入新节点后,调用fixAfterInsertion(e)方法来修复红黑树。

看一下get方法,这里会调用getEntry方法,就是二叉查找树的查找:

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();
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;
}

还有一个remove方法,这里最后调用的是deleteEntry()方法,在deleteEntry()方法中最后调用fixAfterDeletion方法来修复树的顺序。

红黑树的删除操作复杂的让人发指,对着CLRS慢慢看吧:

public V remove(Object key) {
Entry<K,V> p = getEntry(key);
if (p == null)
return null; V oldValue = p.value;
deleteEntry(p);
return oldValue;
}
private void deleteEntry(Entry<K,V> p) {
modCount++;
size--; // If strictly internal, copy successor's element to p and then make p
// point to successor.
if (p.left != null && p.right != null) {
Entry<K,V> s = successor(p);
p.key = s.key;
p.value = s.value;
p = s;
} // p has 2 children // Start fixup at replacement node, if it exists.
Entry<K,V> replacement = (p.left != null ? p.left : p.right); if (replacement != null) {
// Link replacement to parent
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; // Null out links so they are OK to use by fixAfterDeletion.
p.left = p.right = p.parent = null; // Fix replacement
if (p.color == BLACK)
fixAfterDeletion(replacement);
} else if (p.parent == null) { // return if we are the only node.
root = null;
} else { // No children. Use self as phantom replacement and unlink.
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;
}
}
}

上面所做的一切繁琐操作都是为了红黑树的基本性质,而修复顺序的操作中最基本的就是左旋和右旋了,下面是左旋和右选的源码。

/** From CLR */
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;
}
} /** From CLR */
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;
}
}

其实所有的操作都是关于红黑树的操作,

决定了TreeMap的有序性,对于TreeMap的增删改查的效率都是O(Log(n))的。

到这里,TreeMap其实就差不多了,最关键的还是对红黑树的操作,希望这种数据结构的知识能掌握的比较扎实吧,多看书,多编程,夯实基础,与诸君共勉。

Java集合之TreeMap的更多相关文章

  1. 【java基础】java集合之TreeMap

    转载文章转载请注明出处:http://www.cnblogs.com/skywang12345/admin/EditPosts.aspx?postid=3310928 第1部分 TreeMap介绍 T ...

  2. Java集合:TreeMap源码剖析

    一.概念 TreeMap是基于红黑树结构实现的一种Map,要分析TreeMap的实现首先就要对红黑树有所了解. 要了解什么是红黑树,就要了解它的存在主要是为了解决什么问题,对比其他数据结构比如数组,链 ...

  3. 死磕 java集合之TreeMap源码分析(一)- 内含红黑树分析全过程

    欢迎关注我的公众号"彤哥读源码",查看更多源码系列文章, 与彤哥一起畅游源码的海洋. 简介 TreeMap使用红黑树存储元素,可以保证元素按key值的大小进行遍历. 继承体系 Tr ...

  4. Java集合之TreeMap源码分析

    一.概述 TreeMap是基于红黑树实现的.由于TreeMap实现了java.util.sortMap接口,集合中的映射关系是具有一定顺序的,该映射根据其键的自然顺序进行排序或者根据创建映射时提供的C ...

  5. 死磕 java集合之TreeMap源码分析(四)-内含彩蛋

    欢迎关注我的公众号"彤哥读源码",查看更多源码系列文章, 与彤哥一起畅游源码的海洋. 二叉树的遍历 我们知道二叉查找树的遍历有前序遍历.中序遍历.后序遍历. (1)前序遍历,先遍历 ...

  6. 死磕 java集合之TreeMap源码分析(三)- 内含红黑树分析全过程

    欢迎关注我的公众号"彤哥读源码",查看更多源码系列文章, 与彤哥一起畅游源码的海洋. 删除元素 删除元素本身比较简单,就是采用二叉树的删除规则. (1)如果删除的位置有两个叶子节点 ...

  7. 死磕 java集合之TreeMap源码分析(二)- 内含红黑树分析全过程

    欢迎关注我的公众号"彤哥读源码",查看更多源码系列文章, 与彤哥一起畅游源码的海洋. 插入元素 插入元素,如果元素在树中存在,则替换value:如果元素不存在,则插入到对应的位置, ...

  8. 给jdk写注释系列之jdk1.6容器(13)-总结篇之Java集合与数据结构

         是的,这篇blogs是一个总结篇,最开始的时候我提到过,对于java容器或集合的学习也可以看做是对数据结构的学习与应用.在前面我们分析了很多的java容器,也接触了好多种常用的数据结构,今天 ...

  9. 死磕 java集合之终结篇

    概览 我们先来看一看java中所有集合的类关系图. 这里面的类太多了,请放大看,如果放大还看不清,请再放大看,如果还是看不清,请放弃. 我们下面主要分成五个部分来逐个击破. List List中的元素 ...

随机推荐

  1. asp 实现域名转向

    <% host=lcase(request.servervariables("HTTP_HOST")) select CASE host CASE "aa.cn&q ...

  2. gulp.js简单操作

    一.安装gulp 1.深入设置任务之前,需先安装gulp: $ npm install gulp -g 2.这会将gulp安装到全域环境下,让你可以存取gulp的CLI.接著,需要在本地端的专案进行安 ...

  3. Apache Shiro 使用手册(三)Shiro 授权

    授权即访问控制,它将判断用户在应用程序中对资源是否拥有相应的访问权限. 如,判断一个用户有查看页面的权限,编辑数据的权限,拥有某一按钮的权限,以及是否拥有打印的权限等等. 一.授权的三要素 授权有着三 ...

  4. Android学习笔记(八)

    android中常见空间的使用方法 1.TextView TextView主要用于在界面上显示一段文本信息,如下面代码所示: <LinearLayout xmlns:android=" ...

  5. ConfigParser.MissingSectionHeaderError: File contains no section headers.

    今天使用ConfigParser解析一个ini文件,报出如下错误: config.read(logFile) File "C:\Python26\lib\ConfigParser.py&qu ...

  6. java io流 数据流传输

    java io流 数据流传输 把这段当公式用就可以了 //数据流传输 import java.io.*; public class Index{ public static void main(Str ...

  7. Spring4.1新特性——Spring MVC增强

    目录 Spring4.1新特性——综述 Spring4.1新特性——Spring核心部分及其他 Spring4.1新特性——Spring缓存框架增强 Spring4.1新特性——异步调用和事件机制的异 ...

  8. 40. Interleaving String

    Interleaving String Given s1, s2, s3, find whether s3 is formed by the interleaving of s1 and s2. Fo ...

  9. 八、MPxToolCommand, tool command

    1. Tool Property Sheets: 是用来更改context属性的编辑框,类似于attribute editor.(property和attribute本质上是一个意思)作用于activ ...

  10. NOIP模拟赛-时间与空间之旅

    题目描述 公元22××年,宇宙中最普遍的交通工具是spaceship.spaceship的出现使得星系之间的联系变得更为紧密,所以spaceship船长也成了最热门的职业之一.当然,要成为一名出色的船 ...