0. 前言

本文对TreeMap的源码进行了解析,本文原创,转载请注明出处:http://blog.csdn.net/seu_calvin/article/details/52746000

先对TreeMap的特性进行一个概述:

(1)TreeMap 是一个有序的key-value集合,它是通过红黑树实现的。因为红黑树是平衡的二叉搜索树,所以其put(包含update操作)、get、remove的时间复杂度都为log(n)。

(2)TreeMap 相比于HashMap多实现了了NavigableMap接口(也就是这个接口,决定了TreeMap与HashMap的不同:HashMap的key是无序的,TreeMap的key是有序的)。

public class TreeMap<K,V>
extends AbstractMap<K,V>
implements NavigableMap<K,V>, Cloneable, java.io.Serializable

(3)TreeMap是非同步的。

1.  红黑树

红黑树是一种二叉搜索树,让我们在一起回忆下二叉搜索树的一些性质:

二叉搜索树左子树的值小于根节点,右子树的值大于根节点。很明显,二叉搜索树每进行一次判断就是能将问题的规模减少一半,所以二叉搜索树查找元素的时间复杂度为log(n)。下面在看一下红黑树的样子:

叶子节点为上图中的NIL节点,国内一些教材中没有这个NIL节点,我们在画图时有时也会省略这些NIL节点,但是我们需要明确,当我们说叶子节点时,指的就是这些NIL节点。

红黑树通过下面5条规则,保证了树是平衡的:

(1)树的节点只有红与黑两种颜色。

(2)根节点为黑色的。

(3)叶子节点为黑色的。

(4)红色节点的子节点必定是黑色的。

(5)从任意节点出发,到其后继的叶子节点的路径中,黑色节点的数目相同。

满足了上面5个条件后,就能够保证根节点到叶子节点的最长路径不会大于根节点到叶子最短路径的2倍。

简单证明如下:假设根节点到叶子节点最短的路径中,黑色节点数目为B,那么根据性质5,根节点到叶子节点的最长路径中,黑色节点数目也是B,最长的情况就是每两个黑色节点中间有个红色节点(也就是红黑相间的情况),所以红色节点最多为B-1个。所以B+B-1<=2B得证。

红黑树操作包括插入、删除、左旋、右旋,这里有个可视化的红黑树操作网站,把这些操作都按动画做出来了,生动形象。

2.  NavigableMap接口

public interface NavigableMap<K,V> extends SortedMap<K,V>
public interface SortedMap<K,V> extends Map<K,V>

TreeMap实现了NavigableMap接口,NavigableMap是JDK1.6新增的,NavigableMap继承了SortedMap(这个Map是有序的),顺序一般是指由Comparable接口提供的keys的自然序,或者也可以在创建SortedMap实例时,指定一个Comparator来决定。插入SortedMap中的key的类都必须继承Comparable类(或指定一个comparator),这样才能通过k1.compareTo(k2)或comparator.compare(k1, k2)来比较两个key。

NavigableMap接口在SortedMap的基础上增加了一些导航方法来返回与搜索目标最近的元素。例如下面这些方法:

lowerEntry//返回所有比给定Map.Entry小的元素
floorEntry//返回所有比给定Map.Entry小或相等的元素
ceilingEntry//返回所有比给定Map.Entry大或相等的元素
higherEntry//返回所有比给定Map.Entry大的元素


3.  TreeMap的存储结构

Entry作为TreeMap存储的结点,包括键、值、父结点、左孩子、右孩子、颜色。源码如下所示:

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;
}
......
}

4.  TreeMap的构造方法

TreeMap有四种构造函数,分别对应不同的参数。不同构造方法的功能已经在注释里标明了。

//1.键自然顺序
public TreeMap() {
comparator = null;
}
//2.根据给定比较器进行排序
public TreeMap(Comparator<? super K> comparator){
this.comparator = comparator;
}
//3.构造一个与给定映射具有相同映射关系的新的树映射,该映射根据其键的自然顺序进行排序
public TreeMap(Map<? extends K, ? extends V> m) {
comparator = null;
putAll(m);
}
// putAll()将m中的所有元素添加到TreeMap中
public void putAll(Map<? extends K, ? extends V> m) {
for (Map.Entry<? extends K, ? extends V> e : m.entrySet())
put(e.getKey(), e.getValue());
} //4.构造一个与指定有序映射具有相同映射关系和相同排序顺序的新的树映射
public TreeMap(SortedMap<K, ? extends V> m){
comparator = m.comparator();
try{
buildFromSorted(m.size(), m.entrySet().iterator(), null, null);}
catch (Exception e){}
}


4.  TreeMap的put操作

public V put(K key, V value) {
Entry<K,V> t = root;
//若根节点为空,则以(key,value)为参数新建节点
if (t == null){
compare(key, key); // type check
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) //表示新增节点的key小于当前及节点的key,准备工作
t = t.left;
else if (cmp > 0) //表示新增节点的key大于当前及节点的key,准备工作
t = t.right;
else
return t.setValue(value); //相等则覆盖旧值
}
while (t != null); //直到把新节点要插入的位置置空
}
//如果没有定义比较器,则采用默认的排序算法进行创建TreeMap集合
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);
}
//将新增节点当做parent的子节点
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)TreeMap的查询和更新都涉及比较操作,故TreeMap的键必须实现Comparable接口或者构造时传入比较器(比较器优先级更高)。

(2)默认的排序算法中put操作不允许null键,但是值(value)允许为null;若传入自定义比较器,可以手动处理null键的情况。

(3)键重复的情况下,新值会覆盖掉旧值。


6.  TreeMap的get操作

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();
Comparable<? super K> k = (Comparable<? super K>) key;
Entry<K,V> p = root;
while (p != null) {//根据Comparable接口定义的比较规则查找
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) {//根据比较器定义的比较规则查找
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;
}

7.  TreeMap的remove操作

public V remove(Object key) {
Entry<K,V> p = getEntry(key);//首先找到待删结点
if (p == null)
return null;
V oldValue = p.value;
deleteEntry(p);//删除结点,并修复红黑树
return oldValue;
}

8.  TreeMap的两种迭代操作

//1.根据entrySet()和Iterator迭代器遍历
Integer integ = null;
Iterator iter = map.entrySet().iterator();
while(iter.hasNext()) {
Map.Entry entry = (Map.Entry)iter.next();
key = (String)entry.getKey();
integ = (Integer)entry.getValue();
}
//2.根据keySet()和Iterator迭代器遍历
String key = null;
Integer integ = null;
Iterator iter = map.keySet().iterator();
while (iter.hasNext()) {
key = (String)iter.next();
integ = (Integer)map.get(key);
}

9.  TreeMap和HashMap的关系

9.1    TreeMap和HashMap的相同点

(1)两者均是线程不安全的。

(2)两者插入节点时,key重复均覆盖旧值。

9.2  TreeMap和HashMap的不同点

(1)HashMap的实现基于数组和单链表,而TreeMap的实现基于红黑树,必要时数组会扩容、保持树平衡。

(2)HashMap的key是无序的,TreeMap的key是有序的。

(3)TreeMap要求key必须实现Comparable接口,或者初始化时传入Comparator比较器。

(4)HashMap增删改查操作的时间复杂度为O(1),TreeMap增删改查操作的时间复杂度为O(log(n))。

(5)HashMap允许null值,TreeMap中默认的排序算法中put操作不允许null键,但是值(value)允许为null;若传入自定义比较器,可以手动处理null键的情况。

10.  TreeMap和TreeSet的关系

TreeMap 是 Map 接口的常用实现类,而TreeSet 是 Set 接口的常用实现类。虽然 TreeMap 和TreeSet 实现的接口规范不同,但 TreeSet 底层是通过 TreeMap 来实现的(如同HashSet底层是是通过HashMap来实现、LinkedHashSet底层基于LinkedHashMap实现一样),因此二者的实现方式完全一样,都是红黑树算法。

相同点:

(1)TreeMap和TreeSet都是有序的集合(仅仅key对象有序)。

(2)TreeMap和TreeSet都是非同步集合,不过都可以使用Collections.synchroinzedMap()来实现同步。

(3)内部对元素的操作时间复杂度都是O(logN),而HashMap/HashSet/HashTable则为O(1)。

不同点:

最主要的区别就是TreeSet和TreeMap分别实现Set和Map接口,相同的道理可以用于HashSet与HashMap、LinkedHashMap与LinkedHashSet。

TreeSet只存储一个对象(通过Map的Key的部分实现,下面贴出的HashSet的构造方法源码可以证明),而TreeMap存储两个对象Key和Value,前者通过put()将元素放入Map,后者使用add()将元素放入Set。HashSet内部是大量借用HashMap的实现,它本身不过是调用HashMap的一个代理。

private transient HashMap<E,Object> map;
// Dummy value to associate with an Object in the backing Map
private static final Object PRESENT = new Object(); /**
* Constructs a new, empty set; the backing HashMap instance has
* default initial capacity (16) and load factor (0.75).
*/
public HashSet() {
map = new HashMap<E,Object>();
}
public boolean add(E e) {
return map.put(e, PRESENT)==null;
} public boolean remove(Object o) {
return map.remove(o)==PRESENT;
} public boolean contains(Object o) {
return map.containsKey(o);
}

转载请注明出处:http://blog.csdn.net/seu_calvin/article/details/52746000

同时请多点赞多多支持~

Java集合——TreeMap源码详解的更多相关文章

  1. Java集合——ArrayList源码详解

    ) ArrayList 实现了RandomAccess, Cloneable, java.io.Serializable三个标记接口,表示它自身支持快速随机访问,克隆,序列化. public clas ...

  2. Java集合——LinkedList源码详解

    )LinkedList直接继承于AbstractSequentialList,同时实现了List接口,也实现了Deque接口. AbstractSequentialList为顺序访问的数据存储结构提供 ...

  3. Java集合——LinkedHashMap源码详解

    个KV.LinkedHashMap不仅像HashMap那样对其进行基于哈希表和单链表的Entry数组+ next链表的存储方式,而且还结合了LinkedList的优点,为每个Entry节点增加了前驱和 ...

  4. java 基础数据结构源码详解及数据结构算法

    http://www.cnblogs.com/skywang12345/category/455711.html http://www.cnblogs.com/liqiu/p/3302607.html

  5. 数据结构与算法系列2 线性表 使用java实现动态数组+ArrayList源码详解

    数据结构与算法系列2 线性表 使用java实现动态数组+ArrayList源码详解 对数组有不了解的可以先看看我的另一篇文章,那篇文章对数组有很多详细的解析,而本篇文章则着重讲动态数组,另一篇文章链接 ...

  6. 【java集合框架源码剖析系列】java源码剖析之TreeMap

    注:博主java集合框架源码剖析系列的源码全部基于JDK1.8.0版本.本博客将从源码角度带领大家学习关于TreeMap的知识. 一TreeMap的定义: public class TreeMap&l ...

  7. Java源码详解系列(十)--全面分析mybatis的使用、源码和代码生成器(总计5篇博客)

    简介 Mybatis 是一个持久层框架,它对 JDBC 进行了高级封装,使我们的代码中不会出现任何的 JDBC 代码,另外,它还通过 xml 或注解的方式将 sql 从 DAO/Repository ...

  8. Activiti架构分析及源码详解

    目录 Activiti架构分析及源码详解 引言 一.Activiti设计解析-架构&领域模型 1.1 架构 1.2 领域模型 二.Activiti设计解析-PVM执行树 2.1 核心理念 2. ...

  9. 源码详解系列(七) ------ 全面讲解logback的使用和源码

    什么是logback logback 用于日志记录,可以将日志输出到控制台.文件.数据库和邮件等,相比其它所有的日志系统,logback 更快并且更小,包含了许多独特并且有用的特性. logback ...

随机推荐

  1. webservice双向验证

    ServicePointManager.Expect100Continue = true; ServicePointManager.SecurityProtocol = SecurityProtoco ...

  2. 如何判断一个整数是否是2的N次幂

    static bool CheckPowerOfTwo(ulong num) { && (num & (num - )) == ; }

  3. 为什么有时候NSData转换成NSString的时候返回nil

    为什么有时候NSData转换成NSString的时候返回nil 有时候,NSData明明有值,可是,当转换成NSString的时候,却没有值,现在来进行测试:) -现在提供测试用素材- 源码如下: / ...

  4. CSS学习摘要-层叠和继承

    当有多个选择器作用在一个元素上时,哪个规则最终会应用到元素上? 其实这是通过层叠机制来控制的,这也和样式继承(元素从其父元素那里获得属性值)有关. 元素的最终样式可以在多个地方定义,它们以复杂的形式相 ...

  5. AT89S52之串行异步通信笔记

    SRF 中断入口地址 中断源 外中断 外部中断0 INT0(P3.2) 外部中断1 INT1(P3.3) 电平方式触发 低电平 脉冲方式触发 脉冲后延的负跳 内中断 定时中断 串行中断 中断允许控制寄 ...

  6. September 19th 2017 Week 38th Tuesday

    Live boldly. Push yourself. Don't settle. 勇敢生活,突破自我,永不设限! Don't indulge in the past, whether it was ...

  7. Ajax请求:本地跨域的问题

    问题出现一: 1.Cross origin requests are only supported for protocol schemes: http, data, chrome, chrome-e ...

  8. #001 Emmet的API图片

    这个是一张Emmet的快捷键图片,里面包含了所有的快捷键. 虽然有很多的快捷键,但是常用的也就那么几个   .   样式 #  ID >  上下级节点 +  .col-md-8+.col-md- ...

  9. Outliner大纲式笔记软件介绍

    简介 什么是Outliner An outliner (or outline processor) is a specialized type of word processor used to vi ...

  10. 批量删除Redis中的数据

    测试环境上是docker安装的redis,生产上使用的是阿里云Redis服务,需要批量清理生产上的数据. 阿里云提供了BS结构的工具管理Redis,但是不能全选批量删除,只能脚本删除,方法是在测试环境 ...