博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
面试官:从源码分析一下TreeSet(基于jdk1.8)
阅读量:4034 次
发布时间:2019-05-24

本文共 4660 字,大约阅读时间需要 15 分钟。

这个TreeSet其实和HashSet类似。HashSet底层是通过HashMap实现的,TreeSet其实底层也是通过TreeMap实现的。这篇文章就通过源码来分析一下TreeSet。

一、简介

TreeSet的作用是保存无重复的数据,不过还对这些数据进行了排序。TreeMap的底层是通过红黑树实现的,所以TreeSet底层也是通过红黑树实现的。TreeSet最主要的特点就是对元素进行了排序。我们对其特点进行总结一下:

(1)TreeSet是基于TreeMap的NavigableSet实现。

(2)TreeSet的元素存储在TreeMap中的key中,TreeMap的value是一个常量对象。

(3)非线程安全 。

(4)java8新增分割器spliterator() 方法。

在源码分析中我们也是基于jdk1.8来分析。下面我们就直接来看一下;

二、源码分析

1、继承关系

//继承了AbstractSet抽象类java.lang.Object        java.util.AbstractCollection
java.util.AbstractSet
java.util.TreeSet
//实现了NavigableSet接口:支持导航方法,比如查找//实现了Cloneable接口:意味着它能被克隆//实现了Serializable接口:意味着被序列化public class TreeSet
extends AbstractSet
implements NavigableSet
, Cloneable, java.io.Serializable

2、参数变量

//存放元素的集合private transient NavigableMap
m;//PRESENT就是上面NavigableMap中的Objectprivate static final Object PRESENT = new Object();

第一个疑问:不是说TreeSet是基于TreeMap实现的,为什么这里的参数是NavigableMap?

这是因为TreeMap实现了NavigableMap接口。

第二个疑问,既然TreeSet只使用了NavigableMap的key,那么直接使用null作为NavigableMap的value不就完了,还方面节省内存。

这个疑问就要好好说一下了,如果看过我HashSet那篇文章想必你一定很清楚了。这里可以直接跳过,如果你没看过,我们直接定位到TreeSet的增删方法。深入源码中找寻答案。

//add方法其实就是Map的put方法public boolean add(E e) {
return m.put(e, PRESENT)==null;}//remove方法其实就是Map的remove方法public boolean remove(Object o) {
return m.remove(o)==PRESENT;}

在add方法中,我们会出现两种put情况

情况1:元素e不重复,直接插入即可。

情况2:元素e重复,m.put(e, PRESENT)一看e这个key已经存在了,就会将PRESENT替换掉相应的value值。然后map返回这个value。value不等于null,于是返回false。很明显插入失败了。

但是如果我们把PRESENT替换成null呢?这时候m.put(e, PRESENT)一看e这个key已经存在了,于是map返回null。完了这时候你会发现null等于null。整个add方法返回的就是true。这不就矛盾了嘛。命名插入的是重复数据,返回的结果依然还是true。所以这里使用了PRESENT这个对象就能有效地避免这种情况。

3、构造方法

TreeSet(NavigableMap
m) {
this.m = m; } public TreeSet() {
this(new TreeMap
()); } public TreeSet(Comparator
comparator) {
this(new TreeMap<>(comparator)); } public TreeSet(Collection
c) {
this(); addAll(c); } public TreeSet(SortedSet
s) {
this(s.comparator()); addAll(s); }

这五个构造方法,基本上把HashSet的几个特性全部说完了,底层就是new一个TreeMap来实现的,我们还可以假如自己的比较器和排序器。

4、其他方法

增删方法我们已经看了其实就是通过map的put的remove方法来实现的。我们来看一些其他的常见方法

(1)返回子Set

//底层其实就是通过TreeMap的subMap()实现的public NavigableSet
subSet(E fromElement, boolean fromInclusive, E toElement, boolean toInclusive) {
return new TreeSet<>(m.subMap(fromElement, fromInclusive, toElement, toInclusive));}

(2)返回Set的头部和尾部

// 返回Set的头部,范围是:从头部到toElement。// inclusive:是否包含toElement的标志public NavigableSet
headSet(E toElement, boolean inclusive) {
return new TreeSet
(m.headMap(toElement, inclusive));}// 返回Set的尾部,范围是:从fromElement到结尾。public NavigableSet
tailSet(E fromElement, boolean inclusive) {
return new TreeSet
(m.tailMap(fromElement, inclusive));}

(3)返回set中大于小于等于e的元素

// 返回Set的第一个元素public E first() {
return m.firstKey(); }// 返回Set的最后一个元素public E last() {
return m.lastKey(); }// 返回Set中小于e的最大元素public E lower(E e) {
return m.lowerKey(e); }// 返回Set中小于/等于e的最大元素public E floor(E e) {
return m.floorKey(e);}// 返回Set中大于/等于e的最小元素public E ceiling(E e) {
return m.ceilingKey(e);}// 返回Set中大于e的最小元素public E higher(E e) {
return m.higherKey(e);}

(4)获取首尾元素并移除

// 获取第一个元素,并将该元素从TreeMap中删除。public E pollFirst() {
Map.Entry
e = m.pollFirstEntry(); return (e == null)? null : e.getKey();}// 获取最后一个元素,并将该元素从TreeMap中删除。public E pollLast() {
Map.Entry
e = m.pollLastEntry(); return (e == null)? null : e.getKey();}

(5)读写数据

// 将TreeSet的“比较器、容量,所有的元素值”都写入到输出流中private void writeObject(java.io.ObjectOutputStream s)    throws java.io.IOException {
s.defaultWriteObject(); // 写入比较器 s.writeObject(m.comparator()); // 写入容量 s.writeInt(m.size()); // 写入“TreeSet中的每一个元素” for (Iterator i=m.keySet().iterator(); i.hasNext(); ) s.writeObject(i.next());}

还有一个读数据

private void readObject(java.io.ObjectInputStream s)    throws java.io.IOException, ClassNotFoundException {
s.defaultReadObject(); // 从输入流中读取TreeSet的“比较器” Comparator
c = (Comparator
) s.readObject(); TreeMap
tm; if (c==null) tm = new TreeMap
(); else tm = new TreeMap
(c); m = tm; int size = s.readInt(); // 读取TreeSet的全部元素 tm.readTreeSet(size, s, PRESENT);}

源码很简单基本上都在这。下面我们总结一下。

对于HashSet是用Hash表来存储数据,而TreeSet是用二叉树来存储数据。 在不需要排序的时候,还是建议优先使用HashSet,因为速度更快,二叉树需要排序就免不了跳转旋转,所以速度会很慢。

感谢大家一直以来的支持,今天的文章先写到这。

在这里插入图片描述

转载地址:http://hnbdi.baihongyu.com/

你可能感兴趣的文章
python一句话之利用文件对话框获取文件路径
查看>>
PaperDownloader——文献命名6起来
查看>>
PaperDownloader 1.5.1——更加人性化的文献下载命名解决方案
查看>>
如何将PaperDownloader下载的文献存放到任意位置
查看>>
C/C++中关于动态生成一维数组和二维数组的学习
查看>>
JVM最简生存指南
查看>>
漂亮的代码,糟糕的行为——解决Java运行时的内存问题
查看>>
Java的对象驻留
查看>>
logback高级特性使用(二) 自定义Pattern模板
查看>>
JVM并发机制探讨—内存模型、内存可见性和指令重排序
查看>>
可扩展、高可用服务网络设计方案
查看>>
如何构建高扩展性网站
查看>>
微服务架构的设计模式
查看>>
持续可用与CAP理论 – 一个系统开发者的观点
查看>>
nginx+tomcat+memcached (msm)实现 session同步复制
查看>>
c++字符数组和字符指针区别以及str***函数
查看>>
c++类的操作符重载注意事项
查看>>
c++模板与泛型编程
查看>>
STL::deque以及由其实现的queue和stack
查看>>
WAV文件解析
查看>>