一、前言

前一篇博客中,我们对TreeMap的继承关系进行了分析,在这一篇里,我们将分析TreeMap的数据结构,深入理解它的排序能力是如何实现的。这一节要有一定的数据结构基础,在阅读下面的之前,推荐大家先看一下:《算法4》深入理解红黑树。(个人比较喜欢算法四这里介绍的红黑树实现:从2-3树到红黑树的过渡很清晰,虽然源码里的实现不是这种方式 T^T),先了解一下红黑树的由来以及它的特性,这样能更好的理解TreeMap的实现。

二、 TreeMap的结构

TreeMap的内部实现就是一个红黑树。

对于红黑树的定义:

  1. 节点是红色或黑色。
  2. 根是黑色。
  3. 所有叶子都是黑色(叶子是NIL节点)。
  4. 每个红色节点必须有两个黑色的子节点。(从每个叶子到根的所有路径上不能有两个连续的红色节点。)
  5. 从任一节点到其每个叶子的所有简单路径都包含相同数目的黑色节点。

三、Tree源码解析

前一篇博客中,我们已经见过了TreeMap的继承关系,所以这里就不重复了,让我们来看一下它的其他内容。

3.1 TreeMap的成员变量

public class TreeMap<K,V> extends AbstractMap<K,V>
implements NavigableMap<K,V>, Cloneable, java.io.Serializable {
// Key的比较器,用作排序
private final Comparator<? super K> comparator;
//树的根节点
private transient Entry<K,V> root;
//树的大小
private transient int size = 0;
//修改计数器
private transient int modCount = 0;
//返回map的Entry视图
private transient EntrySet entrySet;
private transient KeySet<K> navigableKeySet;
private transient NavigableMap<K,V> descendingMap;
//定义红黑树的颜色
private static final boolean RED = false;
private static final boolean BLACK = true; }

3.2 TreeMap的构造方法

对一些不重要的构造方法就不流水账一样的记录了。

3.2.1 TreeMap(Comparator<? super K> comparator)

public TreeMap(Comparator<? super K> comparator) {
this.comparator = comparator;
}

允许用户自定义比较器进行key的排序。

3.2.2 public TreeMap(Map<? extends K, ? extends V> m)

public TreeMap(Map<? extends K, ? extends V> m) {
comparator = null;
putAll(m);
} public void putAll(Map<? extends K, ? extends V> map) {
int mapSize = map.size();
//判断map是否SortedMap,不是则采用AbstractMap的putAll
if (size==0 && mapSize!=0 && map instanceof SortedMap) {
Comparator<?> c = ((SortedMap<?,?>)map).comparator();
//同为null或者不为null,类型相同,则进入有序map的构造
if (c == comparator || (c != null && c.equals(comparator))) {
++modCount;
try {
buildFromSorted(mapSize, map.entrySet().iterator(),
null, null);
} catch (java.io.IOException cannotHappen) {
} catch (ClassNotFoundException cannotHappen) {
}
return;
}
}
super.putAll(map);
}

buildFromSorted将在后面解析,因为后面的构造函数也调用了这个方法。

3.2.3 public TreeMap(SortedMap<K, ? extends V> 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) {
}
}

下面让我们来看一下这个buildFromSorted方法:

/**
* size: map里键值对的数量
* it: 传入的map的entries迭代器
* str: 如果不为空,则从流里读取key-value
* defaultVal:见名知意,不为空,则value都用这个值
*/
private void buildFromSorted(int size, Iterator<?> it,
java.io.ObjectInputStream str,
V defaultVal)
throws java.io.IOException, ClassNotFoundException {
this.size = size;
root = buildFromSorted(0, 0, size-1, computeRedLevel(size),
it, str, defaultVal);
}

我们先来分析一下computeRedLevel方法:

private static int computeRedLevel(int sz) {
int level = 0;
for (int m = sz - 1; m >= 0; m = m / 2 - 1)
level++;
return level;
}

它的作用是用来计算完全二叉树的层数。什么意思呢,先来看一下下面的图:

把根结点索引看为0,那么高度为2的树的最后一个节点的索引为2,类推高度为3的最后一个节点为6,满足m = (m + 1) * 2。那么计算这个高度有什么好处呢,如上图,如果一个树有9个节点,那么我们构造红黑树的时候,只要把前面3层的结点都设置为黑色,第四层的节点设置为红色,则构造完的树,就是红黑树,满足前面提到的红黑树的5个条件。而实现的关键就是找到要构造树的完全二叉树的层数。

了解了上面的原理,后面就简单了,接着来看buildFromSorted方法:

/**
* level: 当前树的层数,注意:是从0层开始
* lo: 子树第一个元素的索引
* hi: 子树最后一个元素的索引
* redLevel: 上述红节点所在层数
* 剩下的3个就不解释了,跟上面的一样
*/
@SuppressWarnings("unchecked")
private final Entry<K,V> buildFromSorted(int level, int lo, int hi,
int redLevel,
Iterator<?> it,
java.io.ObjectInputStream str,
V defaultVal)
throws java.io.IOException, ClassNotFoundException {
// hi >= lo 说明子树已经构造完成
if (hi < lo) return null;
// 取中间位置,无符号右移,相当于除2
int mid = (lo + hi) >>> 1;
Entry<K,V> left = null;
//递归构造左结点
if (lo < mid)
left = buildFromSorted(level+1, lo, mid - 1, redLevel,
it, str, defaultVal);
K key;
V value;
// 通过迭代器获取key, value
if (it != null) {
if (defaultVal==null) {
Map.Entry<?,?> entry = (Map.Entry<?,?>)it.next();
key = (K)entry.getKey();
value = (V)entry.getValue();
} else {
key = (K)it.next();
value = defaultVal;
}
// 通过流来读取key, value
} else {
key = (K) str.readObject();
value = (defaultVal != null ? defaultVal : (V) str.readObject());
}
//构建结点
Entry<K,V> middle = new Entry<>(key, value, null);
// level从0开始的,所以上述9个节点,计算出来的是3,实际上就是代表的第4层
if (level == redLevel)
middle.color = RED;
//如果存在的话,设置左结点,
if (left != null) {
middle.left = left;
left.parent = middle;
}
// 递归构造右结点
if (mid < hi) {
Entry<K,V> right = buildFromSorted(level+1, mid+1, hi, redLevel,
it, str, defaultVal);
middle.right = right;
right.parent = middle;
}
return middle;
}

**另外提一句,buildFromSorted能这么构造是因为这是一个已经排序的map。

【JDK1.8】JDK1.8集合源码阅读——TreeMap(二)的更多相关文章

  1. 【JDK1.8】JDK1.8集合源码阅读——TreeMap(一)

    一.前言 在前面两篇随笔中,我们提到过,当HashMap的桶过大的时候,会自动将链表转化成红黑树结构,当时一笔带过,因为我们将留在本章中,针对TreeMap进行详细的了解. 二.TreeMap的继承关 ...

  2. 【JDK1.8】Java 8源码阅读汇总

    一.前言 ​ 万丈高楼平地起,相信要想学好java,仅仅掌握基础的语法是远远不够的,从今天起,笔者将和园友们一起阅读jdk1.8的源码,并将阅读重点放在常见的诸如collection集合以及concu ...

  3. 【JDK1.8】JDK1.8集合源码阅读——IdentityHashMap

    一.前言 今天我们来看一下本次集合源码阅读里的最后一个Map--IdentityHashMap.这个Map之所以放在最后是因为它用到的情况最少,也相较于其他的map来说比较特殊.就笔者来说,到目前为止 ...

  4. java1.7集合源码阅读: Stack

    Stack类也是List接口的一种实现,也是一个有着非常长历史的实现,从jdk1.0开始就有了这个实现. Stack是一种基于后进先出队列的实现(last-in-first-out (LIFO)),实 ...

  5. java1.7集合源码阅读: Vector

    Vector是List接口的另一实现,有非常长的历史了,从jdk1.0开始就有Vector了,先于ArrayList出现,与ArrayList的最大区别是:Vector 是线程安全的,简单浏览一下Ve ...

  6. 【详解】ThreadPoolExecutor源码阅读(二)

    系列目录 [详解]ThreadPoolExecutor源码阅读(一) [详解]ThreadPoolExecutor源码阅读(二) [详解]ThreadPoolExecutor源码阅读(三) AQS在W ...

  7. 【原】FMDB源码阅读(二)

    [原]FMDB源码阅读(二) 本文转载请注明出处 -- polobymulberry-博客园 1. 前言 上一篇只是简单地过了一下FMDB一个简单例子的基本流程,并没有涉及到FMDB的所有方方面面,比 ...

  8. 【原】AFNetworking源码阅读(二)

    [原]AFNetworking源码阅读(二) 本文转载请注明出处 —— polobymulberry-博客园 1. 前言 上一篇中我们在iOS Example代码中提到了AFHTTPSessionMa ...

  9. 【原】SDWebImage源码阅读(二)

    [原]SDWebImage源码阅读(二) 本文转载请注明出处 —— polobymulberry-博客园 1. 解决上一篇遗留的坑 上一篇中对sd_setImageWithURL函数简单分析了一下,还 ...

随机推荐

  1. mysql多实例-主从复制安装

    安装环境:Centos6.5 mysql版本:mysql-5.5.32.tar.gz 一:安装前准备: 1.安装一些依赖库 yum install cmake gcc gcc-c++ ncurses- ...

  2. [hihoCoder]矩形判断

    #1040 : 矩形判断 时间限制:1000ms 单点时限:1000ms 内存限制:256MB 描述 给出平面上4条线段,判断这4条线段是否恰好围成一个面积大于0的矩形. 输入 输入第一行是一个整数T ...

  3. JS难点--面向对象(封装)

    我觉得js的难点之一就是面向对象编程. 面向对象 它是一种编程思想,它在写法上比面向过程相对来说复杂一些: 以下是我学习中关于面向对象的知识点总结:   1.什么是对象 从广义上说,"一切皆 ...

  4. Java中Double保留后小数位的几种方法

    最近做个小实验,其中一部分要求将表单提交的数据转换为double,这个功能其实很简单直接一句Double.parseDouble(request.getParameter("chinese& ...

  5. Just a Hook(区间set)

    Just a Hook Time Limit: 4000/2000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others)Total ...

  6. Linux分区规划与xshell使用排错

    1.1 没有重要数据 /boot   200M    存放系统的引导信息 内核 swap   交换分区   防止内存用光了 临时的一个内存 如果你的内存小于8G swap是内存的1.5倍   如果你的 ...

  7. 2017上海QCon之旅总结(下)

    本来这个公众号的交流消息中间件相关的技术的.十月去上海参加了QCon,第一次参加这样的技术会议,感受挺多的,所以整理一下自己的一些想法接公众号和大家交流一下. 三天的内容还挺多的,之前已经有上篇和中篇 ...

  8. Azure ARM虚拟机部署反恶意软件-安全扩展

    Azure虚拟机,默认情况下没有安装杀毒软件.如果您有此需求可以通过Azure 扩展进行安装,有关Azure反恶意软件的官方说明请参考:https://docs.azure.cn/zh-cn/secu ...

  9. svg snap 笔记

    路径中的字母,大写相对于左上角绝对定位,小写相对定位  M110,95,95,110M115,100,100,115   pattern 类似于图片拼贴,可以把指定位置的图案用来填充 var patt ...

  10. day2--计算机基础

    一.服务器 1u服务器,1u=4.45cm 戴尔服务器种类: 电脑主机组成 主板.CPU.内存.硬盘.显卡.声卡等等. 运维关注三大部件:CPU.内存.硬盘(Disk) 电源,考虑使用双电服务器,电源 ...