前言

上篇文章我们分析了HashSet,它是基于HashMap实现的,那TreeSet会是怎么实现的呢?没错!和大家想的一样,它是基于TreeMap实现的。所以,TreeSet的源码也很简单,主要还是理解TreeMap。

TreeSet的继承关系

按照惯例,先来看TreeSet类的继承关系:

public class TreeSet<E> extends AbstractSet<E>
implements NavigableSet<E>, Cloneable, java.io.Serializable
  1. 毫不意外的继承了抽象类AbstracSet,方便扩展;
  2. 实现了一个NavigableSet接口,和NavigableMap接口类似,提供了各种导航方法;
  3. 实现了Cloneable接口,可以克隆;
  4. 实现了Serializable接口,可以序列化;

这里主要看NavigableSet接口类:

public interface NavigableSet<E> extends SortedSet<E>

熟悉的味道,继承SortedSet接口。SortedSet则提供了一个返回比较器的方法:

Comparator<? super E> comparator();

和SortedMap一样,支持自然排序自定义排序。自然排序要求添加到Set中的元素实现Comparable接口,自定义排序要求实现一个Comparator比较器。

源码分析

关键点

关键点自然是TreeSet如何保证元素不重复以及元素有序的,前面说了它是基于TreeMap实现的,那我们来看看吧。

private transient NavigableMap<E,Object> m; // 保证有序

// Dummy value to associate with an Object in the backing Map
private static final Object PRESENT = new Object(); // 固定Value
纵观TreeSet源码,发现只有这两个属性(还有个uid,这里就不算了)。很明显,m是用来保存元素的,但m声明的是NavigableMap而不是TreeMap。可以猜测,TreeMap应该是在构造方法里实例化的,这里使用NavigableMap可以让TreeSet更加灵活。PRESENT和HashSet中的PRESENT作用一样,作为固定Value值进行占位的。
再看addremove方法:
public boolean add(E e) {
return m.put(e, PRESENT)==null;
} public boolean remove(Object o) {
return m.remove(o)==PRESENT;
}

和HashSet的实现一样,也是利用了Map保存的Key-Value键值对的Key不会重复的特点。

构造函数

果然,TreeSet中的TreeMap是在构造函数中初始化的。

public TreeSet() {
this(new TreeMap<>()); // 默认自然排序的TreeMap
} public TreeSet(Comparator<? super E> comparator) {
this(new TreeMap<>(comparator)); // 自定义比较器的TreeMap
} public TreeSet(Collection<? extends E> c) {
this(); // 还是用的默认
addAll(c); // 将元素一个一个添加到TreeMap中
} public TreeSet(SortedSet<E> s) {
this(s.comparator()); // 使用传入的SortedSet的比较器
addAll(s); // 一个一个添加元素
}

默认实例化了一个自然排序的TreeMap,当然,我们可以自定义比较器。
这里跟踪下addAll方法:

public  boolean addAll(Collection<? extends E> c) {
// Use linear-time version if applicable
if (m.size()==0 && c.size() > 0 &&
c instanceof SortedSet &&
m instanceof TreeMap) {
SortedSet<? extends E> set = (SortedSet<? extends E>) c;
TreeMap<E,Object> map = (TreeMap<E, Object>) m; // 强转成TreeMap
Comparator<?> cc = set.comparator();
Comparator<? super E> mc = map.comparator();
if (cc==mc || (cc != null && cc.equals(mc))) { // 要保证set和map的比较器一样
map.addAllForTreeSet(set, PRESENT); // TreeMap专门为TreeSet准备的方法
return true;
}
}
return super.addAll(c);
}

调用了TreeMap的addAllForTreeSet方法:

void addAllForTreeSet(SortedSet<? extends K> set, V defaultVal) {
try {
buildFromSorted(set.size(), set.iterator(), null, defaultVal);
} catch (java.io.IOException | ClassNotFoundException cannotHappen) {
}
}

看到buildFromSorted,应该很熟悉,在TreeMap的文章中分析过。该方法将传入的集合元素构造成了一棵最底层的结点为红色,而其他结点都是黑色的红黑树。

导航方法

既然实现了NavigableSet,那各种导航方法自然少不了。它们的实现也很简单,直接调用m对应的导航方法即可。例如:

public E first() {
return m.firstKey(); // 返回第一个元素
} public E lower(E e) {
return m.lowerKey(e); // 返回小于e的第一个元素
} public NavigableSet<E> headSet(E toElement, boolean inclusive) {
return new TreeSet<>(m.headMap(toElement, inclusive)); // 取前几个元素构成子集
} public E pollFirst() { // 弹出第一个元素
Map.Entry<E,?> e = m.pollFirstEntry();
return (e == null) ? null : e.getKey();
} public NavigableSet<E> descendingSet() { // 倒排Set
return new TreeSet<>(m.descendingMap());
} ......

这里需要注意的是返回子集合的方法,例如:headSet。返回的子集合是可以添加和删除元素的,但是有边界限制,举个栗子。

        // 前面构造了一个存储Int的Set
// 3、5、7、9
SortedSet<Integer> subSet = intSet.headSet(8); // 最大值7,超过7越界
for (Integer sub : subSet) {
System.out.println(sub);
} subSet.add(2);
// subSet.add(8); // 越界了
subSet.remove(3);
for (Integer sub : subSet) {
System.out.println(sub);
}

TreeSet也是支持逆序输出的,因为有descendingIterator的实现:

public Iterator<E> descendingIterator() {
return m.descendingKeySet().iterator();
}

总结

  1. TreeSet是基于TreeMap实现的,支持自然排序和自定义排序,可以进行逆序输出;
  2. TreeSet不允许null值;
  3. TreeSet不是线程安全的,多线程环境下可以使用SortedSet s = Collections.synchronizedSortedSet(new TreeSet(...))

Java常用数据结构之Set之TreeSet的更多相关文章

  1. JAVA常用数据结构及原理分析

    JAVA常用数据结构及原理分析 http://www.2cto.com/kf/201506/412305.html 前不久面试官让我说一下怎么理解java数据结构框架,之前也看过部分源码,balaba ...

  2. (6)Java数据结构-- 转:JAVA常用数据结构及原理分析

    JAVA常用数据结构及原理分析  http://www.2cto.com/kf/201506/412305.html 前不久面试官让我说一下怎么理解java数据结构框架,之前也看过部分源码,balab ...

  3. 【转载】图解Java常用数据结构(一)

    图解Java常用数据结构(一)  作者:大道方圆 原文:https://www.cnblogs.com/xdecode/p/9321848.html 最近在整理数据结构方面的知识, 系统化看了下Jav ...

  4. Java 常用数据结构对象的实现原理 集合类 List Set Map 哪些线程安全 (美团面试题目)

    Java中的集合包括三大类,它们是Set.List和Map, 它们都处于java.util包中,Set.List和Map都是接口,它们有各自的实现类. List.Set都继承自Collection接口 ...

  5. 图解Java常用数据结构(一)【转载】

    最近在整理数据结构方面的知识, 系统化看了下Java中常用数据结构, 突发奇想用动画来绘制数据流转过程. 主要基于jdk8, 可能会有些特性与jdk7之前不相同, 例如LinkedList Linke ...

  6. 图解Java常用数据结构(一)

    最近在整理数据结构方面的知识, 系统化看了下Java中常用数据结构, 突发奇想用动画来绘制数据流转过程. 主要基于jdk8, 可能会有些特性与jdk7之前不相同, 例如LinkedList Linke ...

  7. 图解Java常用数据结构

    最近在整理数据结构方面的知识, 系统化看了下 Java 中常用数据结构, 突发奇想用动画来绘制数据流转过程. 主要基于 jdk8, 可能会有些特性与 jdk7 之前不相同, 例如 LinkedList ...

  8. Java 常用数据结构深入分析(Vector、ArrayList、List、Map)

    线性表,链表,哈希表是常用的数据结构,在进行Java开发时,JDK已经为我们提供了一系列相应的类来实现基本的数据结构.这些类均在java.util包中.本文试图通过简单的描述,向读者阐述各个类的作用以 ...

  9. Java常用数据结构Set, Map, List

    1. Set Set相对于List.Map是最简单的一种集合.集合中的对象不按特定的方式排序,并且没有重复对象. 特点: 它不允许出现重复元素: 不保证和政集合中元素的顺序 允许包含值为null的元素 ...

随机推荐

  1. The Swift.org Blog welcome欢迎页note

    The Swift.org Blog Welcomehtml, body {overflow-x: initial !important;}html { font-size: 14px; } body ...

  2. oozie java api提交作业

    今晚试验用java的api来提交代码,由于代码是在我机器上写的,然后提交到我的虚拟机集群当中去,所以中间产生了一个错误..要想在任意一台机器上向oozie提交作业的话,需要对hadoop的core-s ...

  3. jQuery(十):CSS-DOM操作

    除了css()以外,还有获取和设置元素高度.宽度.相对位置等的样式操作方法,语法如下: 高度和宽度示例: <!DOCTYPE html> <html lang="en&qu ...

  4. Thymeleaf select 数据回显 选中

    <select style='width: 148.15px' name="dlrlx" th:field="*{Ygdly.dlrlx}"> &l ...

  5. Scrapy 导出的 cvs 文件,双击打开乱码问题

    默认可能是utf8格式,在excel的不要直接打开,而是在excel - 数据 -导入里打开,或者打开前用vscode之类的保存为GBK编码

  6. 安卓程序代写 网上程序代写[原]BluetoothDevice详解

    一. BluetoothDevice简介 1. 继承关系 public static Class BluetoothDevice extends Object implement Parcelable ...

  7. 【转】VMware Fusion Professional 10 序列号

    http://bbs.feng.com/forum.php?mod=viewthread&tid=11456176

  8. windows下 删除指定文件夹里面一周前的所有文件和文件夹的bat

    forfiles /p "指定文件夹路径" /m * /s /d -7 /c "cmd /c if @isdir==TRUE (rd /q @path) else del ...

  9. unity----------------3D模型讲解

    图文详解Unity3D中Material的Tiling和Offset是怎么回事 回到顶部(go to top) Tiling和Offset概述 Tiling表示UV坐标的缩放倍数,Offset表示UV ...

  10. (弃) Keystone CLI_选项与子命令概况

    本文档介绍icehouse发行版keystone命令 keystone Command-Line Interface (CLI)提供用于和keystone服务器交互的方便工具,但是该命令行工具逐渐受到 ...