前言

上篇文章我们分析了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. Android,我待你入初恋啊,你就别坑我了!

    最近做了好多东西,东忙忙,西茫茫,ms最后都空欢喜一场. 1.小黄图,说是小黄图,其实只是网上爬下来的写真阿自拍阿什么的,绝对没有反党反共淫秽内容.后来的后来,admob被google停用了,不开心. ...

  2. [转]mysql 行转列 列转行

    原文地址:http://www.cnblogs.com/xiaoxi/p/7151433.html 一.行转列 即将原本同一列下多行的不同内容作为多个字段,输出对应内容. 建表语句 DROP TABL ...

  3. 几种经典的Hash算法的实现(源代码)

    来源声明: http://blog.minidx.com/2008/01/27/446.html 先保存下来,以备后面研究,现在还看不懂! 哈希算法将任意长度的二进制值映射为固定长度的较小二进制值,这 ...

  4. 关于face alglimnment各种资料,存下来有空慢慢看

    人脸对齐和应用 机器学习--详解人脸对齐算法SDM-LBF 基于MTCNN的人脸自动对齐技术原理及其Tensorflow实现测试 人脸检测——MTCNN CVPR论文<Face Alignmen ...

  5. 推荐几个不错的h5游戏引擎

    http://www.phaser.io/examples http://www.cocos.com/ http://cn.cocos2d-x.org/tutorial/lists?id=30 coc ...

  6. vbs中的"WScript.Network"[属性与方法]

    属性ComputerName                  计算机名UserDomain                    所属局域网域的域名UserName                 ...

  7. linq操作符:连接操作符

    linq中的连接操作符主要包括Join()和GroupJoin()两个. 一.Join()操作符 Join()操作符非常类似于T-SQL中的inner join,它将两个数据源进行连接,根据两个数据源 ...

  8. SpringBoot2 使用@RestController 无法返回一个地址,前台无法渲染为页面,只能返回json数据

    spring boot之访问静态页面 楼主前两天自学spring boot,然后在学习的过程中,出现一个疑问,就是如何去访问静态的html网页,这个问题,楼主上网上搜了下,找到的是在resource目 ...

  9. PCL采样一致性算法

    在计算机视觉领域广泛的使用各种不同的采样一致性参数估计算法用于排除错误的样本,样本不同对应的应用不同,例如剔除错误的配准点对,分割出处在模型上的点集,PCL中以随机采样一致性算法(RANSAC)为核心 ...

  10. 【1】JVM-内存模型

    本篇其实就是一个读书笔记,书是<深入理解JAVA虚拟机>,在网上搜索JAVA内存,说的比较好的其实很多都源自这本书,作为一个JAVA程序员,理解虚拟机是通向高级程序员的必经道路.本篇中的图 ...