这是看过的第一个jdk源码(从立下目标以来):TreeMap。说实话断断续续的看了有好几天了,我觉得我犯了一个错误,就像一开始说的那样,我打算完完全全看懂TreeMap关于红黑树的实现方式,后来我想了想,相对于花费这个对我的收益并不是特别大,而且看的过程中也有很多困惑,虽然我知道它每一步在做什么,但是我还不知道为什么要这么做,经过权衡,我决定先暂时放下TreeMap中关于对红黑树具体的实现,暂且记下有这回事。

一、简单理解

  最近也在看数据结构和算法,对红黑树也有了大致的理解,TreeMap是对红黑树的Java实现完美实现版本。本篇不打算讨论TreeMap是怎么实现红黑树的。

1.1 使用Entry做为基本单元

  首先在红黑树中既要保存数据,还要保存数据所处的位置,自然而然就想到用一个对象来包装存储的数据,TreeMap中是用一个静态最终类Entry来实现这个功能的,而这个类是实现自Map的Entry接口:

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;
} public K getKey() {
return key;
} public V getValue() {
return value;
} public V setValue(V value) {
V oldValue = this.value;
this.value = value;
return oldValue;
} public boolean equals(Object o) {
if (!(o instanceof Map.Entry))
return false;
Map.Entry<?,?> e = (Map.Entry<?,?>)o; return valEquals(key,e.getKey()) && valEquals(value,e.getValue());
} public int hashCode() {
int keyHash = (key==null ? 0 : key.hashCode());
int valueHash = (value==null ? 0 : value.hashCode());
return keyHash ^ valueHash;
} public String toString() {
return key + "=" + value;
}
}

  可以看到每一个除了包含自己数据的key和value值,同样有左、右、父节点的变量,并包括一个默认的黑色字段,这样,一个红黑树节点的所有信息就包含进去了,通过不断扩充节点的left、right和parent来描述整个树。

  另外在equals中为了比较两个值是否相等还新建了一个比较方法:

static final boolean valEquals(Object o1, Object o2) {
return (o1==null ? o2==null : o1.equals(o2));
}

  这个比较方法避免了上层对null值的判断,其实在Objects里面已经有了,不过Objects出现的晚,所以说常用的工具方法就应该集中起来,不然每个类都写一遍可不是我心目中的jdk。

1.2 对transient的疑惑

  我对transient还是有基本的了解的,当序列化对象的时候,被transient标记的字段就不会被序列化,因此我看到如下代码时很惊讶:

private transient Entry<K,V> root;
private transient int size = 0;

  我认为根节点和容量是两个很重要的属性,序列化时为什么要去除呢,去除后会有错误的,为了验证我的想法我写了个测试代码:

TreeMap<Integer, Integer> map=new TreeMap<>();
map.put(1, 1);
map.put(2, 1);
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("a.txt"));
oos.writeObject(map);
oos.flush();
oos.close();
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("a.txt"));
TreeMap<Integer, Integer> mapClone = (TreeMap<Integer, Integer>) ois.readObject();
ois.close();
mapClone.put(3, 1);
System.out.println(mapClone);

  并在反序列化对象put新数据的时候,打断点进去看,发现root居然不是null,而size也不是0,也是真实容量2,我晕了,超出了我的理解。经过测试和查找没有解决问题,在这留个坑,希望有人能帮到我。

1.3 对吞异常的反感

  之前我写过一篇博客,是讲解异常的,可以去看一下:Java异常体系简析。所以我对把异常捕获而不做任何处理非常反感,因此看到以下代码,觉得非常不适:

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) {
}
}

  什么叫做cannotHappen,不可能发生那异常还有必要存在吗,我还是认为应该抛一个虚拟机异常。不理解的可以去看我上面说的那篇博客。

1.4 遍历时防止另外线程的修改

  TreeMap不是一个线程安全的类,当你在遍历一个TreeMap的时候,如果有另外一个线程修改了TreeMap,会立即抛异常,是通过一个变量来实现的:

private transient int modCount = 0;

  每次修改都会让这个数加一,然后遍历时每次都会判断这个数是否发生变化:

@Override
public void forEach(BiConsumer<? super K, ? super V> action) {
Objects.requireNonNull(action);
int expectedModCount = modCount;
for (Entry<K, V> e = getFirstEntry(); e != null; e = successor(e)) {
action.accept(e.key, e.value); if (expectedModCount != modCount) {
throw new ConcurrentModificationException();
}
}
}

  这个可以在一定程度上防止遍历时被修改,只是被动防御,但是真要想达到随意的增删改查还是要使用ConcurrentSkipListMap。

1.5 创建时没有值的做法

  当我们需要遍历集合的时候,有一种做法是先获取entrySet,然后遍历entrySet的Entry并获取key和value,在看到下面获取entrySet的代码时:

public Set<Map.Entry<K,V>> entrySet() {
EntrySet es = entrySet;
return (es != null) ? es : (entrySet = new EntrySet());
}

  产生了疑惑,当获取时若为null就去创建一个new EntrySet(),而它的构造方法就是一个空的???,也就是说返回的确实是空的一个EntrySet,但是使用它的时候确实也有值:

TreeMap<Integer,Integer> map=new TreeMap<>();
map.put(1, 1);
Set<Entry<Integer, Integer>> set = map.entrySet();
System.out.println(set);

  我在map.entrySet()这一行,debug进去一步一步看,确实是没有值,找了好一阵子,后来在debug这System.out.println(set)的时候才发现玄机。

  简单来说,当你获得entrySet的时候确实是没有值的,但是它内部持有一个TreeMap的一个遍历器,而在真正使用的时候才去遍历关联的TreeMap,感觉和jdk1.5的Stream一样,因此方法的注释也都说明是一个view。而TreeMap中大量应用了这种方式,我觉得其他集合应该也是这么实现的。

  这就给我们一种编程思路,不是说所有数据在获取的时候都需要给它准备好,等他真正使用的时候再给也不晚。

1.6 对常用方法的封装

  TreeMap值有一些这样的方法:

private static <K,V> boolean colorOf(Entry<K,V> p) {
return (p == null ? BLACK : p.color);
}
private static <K,V> Entry<K,V> parentOf(Entry<K,V> p) {
return (p == null ? null: p.parent);
}
private static <K,V> void setColor(Entry<K,V> p, boolean c) {
if (p != null)
p.color = c;
}
private static <K,V> Entry<K,V> leftOf(Entry<K,V> p) {
return (p == null) ? null: p.left;
}
private static <K,V> Entry<K,V> rightOf(Entry<K,V> p) {
return (p == null) ? null: p.right;
}

  TreeMap中最常用的,判断和获取颜色,获取左右节点等,虽然可以通过【.】直接来获得属性,但是有时候还必须要判断是否是null值,所以当大量需要此处判断的时候,封装方法或许是我们最好的选择。

二、问题及总结

  这个TreeMap代码有3000多行,虽然也有很多注释,代码量依然很大,不过细看起来,只针对红黑树实现的增删改查并没有那么多的代码量,而多出来的实现,只是为了方便使用者提供了各种各样的方法,并因此而新建了很多内部类,而对于用户来说只是感觉到了方便。方法特别多,建议看Java API。稍微总结一点:

  • TreeMap是有序Map,按照指定比较器或者key的比较性
  • TreeMap不是线程安全的

2.1 遗留问题

  看到现在还遗留两个问题:

2.1.1 transient没有生效

  这个我随后会深入测试研究。

2.1.2 红黑树的具体实现方式

  这个我暂时不去研究,等以后需要自己实现的时候,再来找它。

【jdk源码1】TreeMap源码学习的更多相关文章

  1. JDK 1.8源码阅读 TreeMap

    一,前言 TreeMap:基于红黑树实现的,TreeMap是有序的. 二,TreeMap结构 2.1 红黑树结构 红黑树又称红-黑二叉树,它首先是一颗二叉树,它具体二叉树所有的特性.同时红黑树更是一颗 ...

  2. 【JDK】JDK源码分析-TreeMap(2)

    前文「JDK源码分析-TreeMap(1)」分析了 TreeMap 的一些方法,本文分析其中的增删方法.这也是红黑树插入和删除节点的操作,由于相对复杂,因此单独进行分析. 插入操作 该操作其实就是红黑 ...

  3. HashMap与TreeMap源码分析

    1. 引言     在红黑树--算法导论(15)中学习了红黑树的原理.本来打算自己来试着实现一下,然而在看了JDK(1.8.0)TreeMap的源码后恍然发现原来它就是利用红黑树实现的(很惭愧学了Ja ...

  4. TreeMap 源码分析

    简介 TreeMap最早出现在JDK 1.2中,是 Java 集合框架中比较重要一个的实现.TreeMap 底层基于红黑树实现,可保证在log(n)时间复杂度内完成 containsKey.get.p ...

  5. TreeMap源码解析

    1.TreeMap介绍 TreeMap是一个通过红黑树实现有序的key-value集合. TreeMap继承AbstractMap,也即实现了Map,它是一个Map集合 TreeMap实现了Navig ...

  6. 结合java.util.TreeMap源码理解红黑树

    前言 本篇将结合JDK1.6的TreeMap源码,来一起探索红-黑树的奥秘.红黑树是解决二叉搜索树的非平衡问题. 当插入(或者删除)一个新节点时,为了使树保持平衡,必须遵循一定的规则,这个规则就是红- ...

  7. Java数据结构和算法 - TreeMap源码理解红黑树

    前言 本篇将结合JDK1.6的TreeMap源码,来一起探索红-黑树的奥秘.红黑树是解决二叉搜索树的非平衡问题. 当插入(或者删除)一个新节点时,为了使树保持平衡,必须遵循一定的规则,这个规则就是红- ...

  8. Java - TreeMap源码解析 + 红黑树

    Java提高篇(二七)-----TreeMap TreeMap的实现是红黑树算法的实现,所以要了解TreeMap就必须对红黑树有一定的了解,其实这篇博文的名字叫做:根据红黑树的算法来分析TreeMap ...

  9. TreeMap源码分析,看了都说好

    概述 TreeMap也是Map接口的实现类,它最大的特点是迭代有序,默认是按照key值升序迭代(当然也可以设置成降序).在前面的文章中讲过LinkedHashMap也是迭代有序的,不过是按插入顺序或访 ...

  10. 如何查看JDK以及JAVA框架的源码

    如何查看JDK以及JAVA框架的源码 设置步骤如下: 1.点 “window”-> "Preferences" -> "Java" -> &q ...

随机推荐

  1. python应用

    GUI(图形用户界面) python是可以创建GUI的,使用第三方库一般是Tk.wxWidgets.Qt.GTK. 而python自带的是支持Tk的Tkinter,我们这里就来用Tkinter来实现G ...

  2. Metasploit中数据库的密码查看以及使用pgadmin远程连接数据库

    我们都知道,在msf下进行渗透测试工作的时候,可以将结果数据保存到数据库中,方便各个小组成员在渗透测试过程中的数据同步. 例如,Metasploit提供了db_nmap命令,它能够将Nmap扫描结果直 ...

  3. Prototype原型模式(创建型模式)

    1.原型模式解决的问题 现在有一个抽象的游戏设施建造系统,负责构建一个现代风格和古典风格的房屋和道路. 前提:抽象变化较慢,实现变化较快(不稳定) 整个抽象的游戏设施建造系统相对变化较慢,本例中只有一 ...

  4. 字符、字符串和文本的处理之String类型

    .Net Framework中处理字符和字符串的主要有以下这么几个类: (1).System.Char类 一基础字符串处理类 (2).System.String类 一处理不可变的字符串(一经创建,字符 ...

  5. 打印页面时a标签不显示URL的方法

    以前写博客啊,总想写一篇大作,然后希望能挂到博客园首页,隔一会儿看看阅读量有多少.其实哪有那么多大作,大部分时间都是解决了一个小问题,然后需要记录一下.比如下面这篇. 今天遇到一个需求是,打印网页时, ...

  6. 【JAVA】序列化

    好处有2: 实现了数据的持久化,通过序列化可以把数据永久地保存到硬盘上(通常存放在文件里). 利用序列化实现远程通信,即在网络上传送对象的字节序列. 序列化ID的作用: 简单来说,Java的序列化机制 ...

  7. JavaScript -- Window-Focus

    -----034-Window-Focus.html----- <!DOCTYPE html> <html> <head> <meta http-equiv= ...

  8. js便签笔记(10) - 分享:json2.js源码解读笔记

    1. 如何理解“json” 首先应该意识到,json是一种数据转换格式,既然是个“格式”,就是个抽象的东西.它不是js对象,也不是字符串,它只是一种格式,一种规定而已. 这个格式规定了如何将js对象转 ...

  9. Hibernate的执行流程和集合的映射关系

    Hibernate的执行流程 集合映射 准被hibernate的运行环境 配置hibernate.cfg.xml主配置文件 1.Set集合 写User.java类 package com.gqx.co ...

  10. spring boot实现ssm(1)功能

    前面完成了ssm的整合, 整个过程可以说很繁杂, 各种配置, 很容易让人晕掉. 这里使用spring boot 的方式来实现ssm(1)中的功能. 一. 建项目 1. 使用 idea 来创建 spri ...