前言

上篇文章我们分析了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. C# 执行bat批处理文件

    private void RunBat(string batPath) { Process pro = new Process(); FileInfo file = new FileInfo(batP ...

  2. Spring WebSocket初探1 (Spring WebSocket入门教程)<转>

    See more: Spring WebSocket reference整个例子属于WiseMenuFrameWork的一部分,可以将整个项目Clone下来,如果朋友们有需求,我可以整理一个独立的de ...

  3. Go Revel - Websockets

    revel提供了对`Websockets`的支持. 处理`Websockets`链接: 1.添加一个`WS`类型方法的路由 2.添加一个action接受 `*websocket.Conn`参数 例如, ...

  4. face alignment---各种算法框架

    [深度学习]最新的一些开源face alignment及评价 转载  2017年01月12日 11:33:39 2047 dlib :https://github.com/davisking/dlib ...

  5. 解决IDEA 中git 无法自动push 提交问题 Push failed: Failed with error: Could not read from remote repository.

    Push failed: Failed with error: Could not read from remote repository.

  6. PHP 共享内存使用与信号控制

    共享内存 共享内存的使用主要是为了能够在同一台机器不同的进程中共享一些数据,比如在多个 php-fpm 进程中共享当前进程的使用情况.这种通信也称为进程间通信(Inter-Process Commun ...

  7. css之-webkit-scrollbar

    在IE中可以自定义滚动条的样式 ,基于webkit的浏览器现在也可以自定义其样式: ::-webkit-scrollbar              { /* 1 */ }   ::-webkit-s ...

  8. js 实时数据显示

    js代码 <script type="text/javascript" src="jquery.js"></script> <sc ...

  9. 关于Unity中的光照(二)

    光源 1: 光照的本质:就是光的颜色和物体纹理的颜色的混合;2: 光源类型: 点光源,定向光源,聚光灯, 区域光源; 区域光的范围会在场景中用黄色的光显示出来; z轴是光的方向; 光的强度会随距离衰减 ...

  10. Netty 介绍

    本指南对Netty 进行了介绍并指出其意义所在. 1. 问题 现在,我们使用适合一般用途的应用或组件来和彼此通信.例如,我们常常使用一个HTTP客户端从远程服务器获取信息或者通过web service ...