问题

(1)TreeSet真的是使用TreeMap来存储元素的吗?

(2)TreeSet是有序的吗?

(3)TreeSet和LinkedHashSet有何不同?

简介

TreeSet底层是采用TreeMap实现的一种Set,所以它是有序的,同样也是非线程安全的。

源码分析

经过前面我们学习HashSet和LinkedHashSet,基本上已经掌握了Set实现的套路了。

所以,也不废话了,直接上源码:

  1. package java.util;
  2. // TreeSet实现了NavigableSet接口,所以它是有序的
  3. public class TreeSet<E> extends AbstractSet<E>
  4. implements NavigableSet<E>, Cloneable, java.io.Serializable
  5. {
  6. // 元素存储在NavigableMap中
  7. // 注意它不一定就是TreeMap
  8. private transient NavigableMap<E,Object> m;
  9. // 虚拟元素, 用来作为value存储在map中
  10. private static final Object PRESENT = new Object();
  11. // 直接使用传进来的NavigableMap存储元素
  12. // 这里不是深拷贝,如果外面的map有增删元素也会反映到这里
  13. // 而且, 这个方法不是public的, 说明只能给同包使用
  14. TreeSet(NavigableMap<E,Object> m) {
  15. this.m = m;
  16. }
  17. // 使用TreeMap初始化
  18. public TreeSet() {
  19. this(new TreeMap<E,Object>());
  20. }
  21. // 使用带comparator的TreeMap初始化
  22. public TreeSet(Comparator<? super E> comparator) {
  23. this(new TreeMap<>(comparator));
  24. }
  25. // 将集合c中的所有元素添加的TreeSet中
  26. public TreeSet(Collection<? extends E> c) {
  27. this();
  28. addAll(c);
  29. }
  30. // 将SortedSet中的所有元素添加到TreeSet中
  31. public TreeSet(SortedSet<E> s) {
  32. this(s.comparator());
  33. addAll(s);
  34. }
  35. // 迭代器
  36. public Iterator<E> iterator() {
  37. return m.navigableKeySet().iterator();
  38. }
  39. // 逆序迭代器
  40. public Iterator<E> descendingIterator() {
  41. return m.descendingKeySet().iterator();
  42. }
  43. // 以逆序返回一个新的TreeSet
  44. public NavigableSet<E> descendingSet() {
  45. return new TreeSet<>(m.descendingMap());
  46. }
  47. // 元素个数
  48. public int size() {
  49. return m.size();
  50. }
  51. // 判断是否为空
  52. public boolean isEmpty() {
  53. return m.isEmpty();
  54. }
  55. // 判断是否包含某元素
  56. public boolean contains(Object o) {
  57. return m.containsKey(o);
  58. }
  59. // 添加元素, 调用map的put()方法, value为PRESENT
  60. public boolean add(E e) {
  61. return m.put(e, PRESENT)==null;
  62. }
  63. // 删除元素
  64. public boolean remove(Object o) {
  65. return m.remove(o)==PRESENT;
  66. }
  67. // 清空所有元素
  68. public void clear() {
  69. m.clear();
  70. }
  71. // 添加集合c中的所有元素
  72. public boolean addAll(Collection<? extends E> c) {
  73. // 满足一定条件时直接调用TreeMap的addAllForTreeSet()方法添加元素
  74. if (m.size()==0 && c.size() > 0 &&
  75. c instanceof SortedSet &&
  76. m instanceof TreeMap) {
  77. SortedSet<? extends E> set = (SortedSet<? extends E>) c;
  78. TreeMap<E,Object> map = (TreeMap<E, Object>) m;
  79. Comparator<?> cc = set.comparator();
  80. Comparator<? super E> mc = map.comparator();
  81. if (cc==mc || (cc != null && cc.equals(mc))) {
  82. map.addAllForTreeSet(set, PRESENT);
  83. return true;
  84. }
  85. }
  86. // 不满足上述条件, 调用父类的addAll()通过遍历的方式一个一个地添加元素
  87. return super.addAll(c);
  88. }
  89. // 子set(NavigableSet中的方法)
  90. public NavigableSet<E> subSet(E fromElement, boolean fromInclusive,
  91. E toElement, boolean toInclusive) {
  92. return new TreeSet<>(m.subMap(fromElement, fromInclusive,
  93. toElement, toInclusive));
  94. }
  95. // 头set(NavigableSet中的方法)
  96. public NavigableSet<E> headSet(E toElement, boolean inclusive) {
  97. return new TreeSet<>(m.headMap(toElement, inclusive));
  98. }
  99. // 尾set(NavigableSet中的方法)
  100. public NavigableSet<E> tailSet(E fromElement, boolean inclusive) {
  101. return new TreeSet<>(m.tailMap(fromElement, inclusive));
  102. }
  103. // 子set(SortedSet接口中的方法)
  104. public SortedSet<E> subSet(E fromElement, E toElement) {
  105. return subSet(fromElement, true, toElement, false);
  106. }
  107. // 头set(SortedSet接口中的方法)
  108. public SortedSet<E> headSet(E toElement) {
  109. return headSet(toElement, false);
  110. }
  111. // 尾set(SortedSet接口中的方法)
  112. public SortedSet<E> tailSet(E fromElement) {
  113. return tailSet(fromElement, true);
  114. }
  115. // 比较器
  116. public Comparator<? super E> comparator() {
  117. return m.comparator();
  118. }
  119. // 返回最小的元素
  120. public E first() {
  121. return m.firstKey();
  122. }
  123. // 返回最大的元素
  124. public E last() {
  125. return m.lastKey();
  126. }
  127. // 返回小于e的最大的元素
  128. public E lower(E e) {
  129. return m.lowerKey(e);
  130. }
  131. // 返回小于等于e的最大的元素
  132. public E floor(E e) {
  133. return m.floorKey(e);
  134. }
  135. // 返回大于等于e的最小的元素
  136. public E ceiling(E e) {
  137. return m.ceilingKey(e);
  138. }
  139. // 返回大于e的最小的元素
  140. public E higher(E e) {
  141. return m.higherKey(e);
  142. }
  143. // 弹出最小的元素
  144. public E pollFirst() {
  145. Map.Entry<E,?> e = m.pollFirstEntry();
  146. return (e == null) ? null : e.getKey();
  147. }
  148. public E pollLast() {
  149. Map.Entry<E,?> e = m.pollLastEntry();
  150. return (e == null) ? null : e.getKey();
  151. }
  152. // 克隆方法
  153. @SuppressWarnings("unchecked")
  154. public Object clone() {
  155. TreeSet<E> clone;
  156. try {
  157. clone = (TreeSet<E>) super.clone();
  158. } catch (CloneNotSupportedException e) {
  159. throw new InternalError(e);
  160. }
  161. clone.m = new TreeMap<>(m);
  162. return clone;
  163. }
  164. // 序列化写出方法
  165. private void writeObject(java.io.ObjectOutputStream s)
  166. throws java.io.IOException {
  167. // Write out any hidden stuff
  168. s.defaultWriteObject();
  169. // Write out Comparator
  170. s.writeObject(m.comparator());
  171. // Write out size
  172. s.writeInt(m.size());
  173. // Write out all elements in the proper order.
  174. for (E e : m.keySet())
  175. s.writeObject(e);
  176. }
  177. // 序列化写入方法
  178. private void readObject(java.io.ObjectInputStream s)
  179. throws java.io.IOException, ClassNotFoundException {
  180. // Read in any hidden stuff
  181. s.defaultReadObject();
  182. // Read in Comparator
  183. @SuppressWarnings("unchecked")
  184. Comparator<? super E> c = (Comparator<? super E>) s.readObject();
  185. // Create backing TreeMap
  186. TreeMap<E,Object> tm = new TreeMap<>(c);
  187. m = tm;
  188. // Read in size
  189. int size = s.readInt();
  190. tm.readTreeSet(size, s, PRESENT);
  191. }
  192. // 可分割的迭代器
  193. public Spliterator<E> spliterator() {
  194. return TreeMap.keySpliteratorFor(m);
  195. }
  196. // 序列化id
  197. private static final long serialVersionUID = -2479143000061671589L;
  198. }

源码比较简单,基本都是调用map相应的方法。

总结

(1)TreeSet底层使用NavigableMap存储元素;

(2)TreeSet是有序的;

(3)TreeSet是非线程安全的;

(4)TreeSet实现了NavigableSet接口,而NavigableSet继承自SortedSet接口;

(5)TreeSet实现了SortedSet接口;(彤哥年轻的时候面试被问过TreeSet和SortedSet的区别^^)

彩蛋

(1)通过之前的学习,我们知道TreeSet和LinkedHashSet都是有序的,那它们有何不同?

LinkedHashSet并没有实现SortedSet接口,它的有序性主要依赖于LinkedHashMap的有序性,所以它的有序性是指按照插入顺序保证的有序性;

而TreeSet实现了SortedSet接口,它的有序性主要依赖于NavigableMap的有序性,而NavigableMap又继承自SortedMap,这个接口的有序性是指按照key的自然排序保证的有序性,而key的自然排序又有两种实现方式,一种是key实现Comparable接口,一种是构造方法传入Comparator比较器。

(2)TreeSet里面真的是使用TreeMap来存储元素的吗?

通过源码分析我们知道TreeSet里面实际上是使用的NavigableMap来存储元素,虽然大部分时候这个map确实是TreeMap,但不是所有时候都是TreeMap。

因为有一个构造方法是TreeSet(NavigableMap<E,Object> m),而且这是一个非public方法,通过调用关系我们可以发现这个构造方法都是在自己类中使用的,比如下面这个:

  1. public NavigableSet<E> tailSet(E fromElement, boolean inclusive) {
  2. return new TreeSet<>(m.tailMap(fromElement, inclusive));
  3. }

而这个m我们姑且认为它是TreeMap,也就是调用TreeMap的tailMap()方法:

  1. public NavigableMap<K,V> tailMap(K fromKey, boolean inclusive) {
  2. return new AscendingSubMap<>(this,
  3. false, fromKey, inclusive,
  4. true, null, true);
  5. }

可以看到,返回的是AscendingSubMap对象,这个类的继承链是怎么样的呢?

可以看到,这个类并没有继承TreeMap,不过通过源码分析也可以看出来这个类是组合了TreeMap,也算和TreeMap有点关系,只是不是继承关系。

所以,TreeSet的底层不完全是使用TreeMap来实现的,更准确地说,应该是NavigableMap。


欢迎关注我的公众号“彤哥读源码”,查看更多源码系列文章, 与彤哥一起畅游源码的海洋。

死磕 java集合之TreeSet源码分析的更多相关文章

  1. 死磕 java集合之DelayQueue源码分析

    问题 (1)DelayQueue是阻塞队列吗? (2)DelayQueue的实现方式? (3)DelayQueue主要用于什么场景? 简介 DelayQueue是java并发包下的延时阻塞队列,常用于 ...

  2. 死磕 java集合之PriorityBlockingQueue源码分析

    问题 (1)PriorityBlockingQueue的实现方式? (2)PriorityBlockingQueue是否需要扩容? (3)PriorityBlockingQueue是怎么控制并发安全的 ...

  3. 死磕 java集合之PriorityQueue源码分析

    问题 (1)什么是优先级队列? (2)怎么实现一个优先级队列? (3)PriorityQueue是线程安全的吗? (4)PriorityQueue就有序的吗? 简介 优先级队列,是0个或多个元素的集合 ...

  4. 死磕 java集合之CopyOnWriteArraySet源码分析——内含巧妙设计

    问题 (1)CopyOnWriteArraySet是用Map实现的吗? (2)CopyOnWriteArraySet是有序的吗? (3)CopyOnWriteArraySet是并发安全的吗? (4)C ...

  5. 死磕 java集合之LinkedHashSet源码分析

    问题 (1)LinkedHashSet的底层使用什么存储元素? (2)LinkedHashSet与HashSet有什么不同? (3)LinkedHashSet是有序的吗? (4)LinkedHashS ...

  6. 死磕 java集合之ConcurrentHashMap源码分析(三)

    本章接着上两章,链接直达: 死磕 java集合之ConcurrentHashMap源码分析(一) 死磕 java集合之ConcurrentHashMap源码分析(二) 删除元素 删除元素跟添加元素一样 ...

  7. 死磕 java集合之ArrayDeque源码分析

    问题 (1)什么是双端队列? (2)ArrayDeque是怎么实现双端队列的? (3)ArrayDeque是线程安全的吗? (4)ArrayDeque是有界的吗? 简介 双端队列是一种特殊的队列,它的 ...

  8. 【死磕 Java 集合】— ConcurrentSkipListMap源码分析

    转自:http://cmsblogs.com/?p=4773 [隐藏目录] 前情提要 简介 存储结构 源码分析 主要内部类 构造方法 添加元素 添加元素举例 删除元素 删除元素举例 查找元素 查找元素 ...

  9. 死磕 java集合之LinkedList源码分析

    问题 (1)LinkedList只是一个List吗? (2)LinkedList还有其它什么特性吗? (3)LinkedList为啥经常拿出来跟ArrayList比较? (4)我为什么把LinkedL ...

随机推荐

  1. Java7里try-with-resources分析

    这个所谓的try-with-resources,是个语法糖.实际上就是自动调用资源的close()函数.和Python里的with语句差不多. 例如: [java] view plain copy   ...

  2. python——在文件存放路径下自动创建文件夹!

    1.a.py文件存放的路径下为(D:\Auto\eclipse\workspace\Testhtml\Test) 2.通过os.getcwd()获取的路径为:D:\Auto\eclipse\works ...

  3. Hadoop配置第1节-集群网络配置

    Hadoop-集群网络配置 总体目标:完成zookeeper+Hadoop+Hbase 整合平台搭建   进度:1:集群网络属性配置2:集群免密码登陆配置3:JDK的安装4:Zookeeper的安装5 ...

  4. Java实现大批量数据导入导出(100W以上) -(一)导入

    最近业务方有一个需求,需要一次导入超过100万数据到系统数据库.可能大家首先会想,这么大的数据,干嘛通过程序去实现导入,为什么不直接通过SQL导入到数据库. 大数据量报表导出请参考:Java实现大批量 ...

  5. Mybatis之旅第五篇-动态SQL

    一.引言 在之前的CRUD例子中,都是一些很简单的SQL,然而实际的业务开发中会有一些复杂的SQL,我们经常需要拼接SQL,拼接的时候要确保不能忘了必要的空格,还要注意省掉列名列表最后的逗号.Myba ...

  6. Docker 堆栈

    1.  Stack stack(译:堆叠,堆栈)是一组相互关联的服务,它们共享依赖关系,并且可以一起编排和伸缩. 在上一篇<Docker 服务>中我们知道可以通过创建一个docker-co ...

  7. 【Android Studio安装部署系列】十七、Android studio引用第三方库、jar、so、arr文件

    版权声明:本文为HaiyuKing原创文章,转载请注明出处! 概述 在Android开发过程,经常需要用到第三方库以及jar.so.arr文件,那么如何引用到项目中呢?下面简单介绍下. 引用第三方库 ...

  8. 用Portable.BouncyCastle来进行加解密的代码demo

    前言 这里对之前对接的公司中的代码demo做一个总结,原本为清一色的java,哈哈.这里都转成C#.用到的库是Portable.BouncyCastle.官网.之前也是准备用.net core 内置的 ...

  9. 【开发记录】如何在B/S项目中使用中国天气的实时天气功能

    好久没有更新我的博客了,正好手头有一个比较合适的项目经验可以分享出来,就是这个如何使用中国天气的天气预报功能,也正好做个项目经验记录. 功能需求 这个功能需求比较简单,就是想在网页端显示实时天气数据. ...

  10. 用css实现正方形div

    目标:实现一个正方形,这个正方形边长等于 方法一:使用单位vw, (ps我觉得这个是最简单的方法) html结构也很简单,只有一个div即可 <html> <body> < ...