前言

上篇文章我们分析了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. 建立window SVN服务器

    在windows下搭建SVN服务器: 首先从http://www.visualsvn.com/server/download/ 下载最新的VisualSVN-Server-x.x.x.msi,然后本机 ...

  2. <[成长股基本面]【怎样选择成长股】>读书笔记

    书在这里 投资想赚大钱,必须有耐性 这家公司的产品或服务有没有充分的市场潜力,至少几年内营业额能否大幅成长? 为了进一步提高总体销售水平,发现新的产品增长点,管理层是不是决心继续开发新产品或新工艺? ...

  3. [cpu]TI cortex-A9 查看cpu的频率

    Hi Aaron, For checking and changing Cortex-A9 CPU frequency in u-boot, refer to the below files: u-b ...

  4. RavenDb学习(四)处理文档相关性

    RavenDb是文档型数据库,但是我们常常也需要定义对象之间的关系,那RavenDb当中是如何处理的呢? RavenDb提供了优雅的解决方式,使用正确的话,可以减少数据开销以及网络拥堵 Denorma ...

  5. 自适应瀑布型布局(手机,PC全兼容)

    有个瀑布型的布局需求,于是找了下,网上大部分用jquery控件,界面太复杂,看起来很笨重,我需求比较简单,于是只能自己写 其实原理就是用cs 去控制,那么我们只需要把对应的界面去规划就行了,比如,我一 ...

  6. Publishing to IIS 发布到IIS

    原文:https://docs.asp.net/en/latest/publishing/iis.html 发布到IIS Publishing to IIS By Rick Anderson and  ...

  7. 微信支付WxpayAPI_php_v3(三)支付成功回调

    接收回调通知后的业务处理都在NotifyProcess做,$data包含了微信返回给你的数据. Service: <?php /** * Created by PhpStorm. * User: ...

  8. js学习(一)-动态添加、修改、删除对象的属性和方法

    //-----------------------js代码--------------------------- function class1(){ } //-------------------- ...

  9. Sublime运行Matlab

    使用Sublime来作为Matlab的编辑器.Sublime编辑+Matlab命令行(terminal)运行的开发环境搭建. 前期准备 Matlab 2014a (Windows或Linux平台,这里 ...

  10. thinkphp中memcache的用法实例

    本文实例讲述了thinkphp中memcache的用法.分享给大家供大家参考.具体分析如下: 1.下载并安装memcache ① window下安装memcache. 下载memcached.exe ...