您的位置 首页 java

Java集合系列-Set系列-TreeSet

这一篇文章是集合的最后一篇文章,好多网友私信我能不能写几篇关于并发包中的文章,因为并发包中大部分类我都仔细研究过,也有自己的理解,所以接下来我计划开始对并发包中的类进行源码分析,如果对你有所帮助,希望大家持续关注,集合中还有两个面试过程中问的比较多的CopyOnWriteArrayList和CopyOnWriteArraySet,这两个属于并发包中的类,我会在接下来的文章详细讲解。ConcurrentHashMap也属于并发包中的类,因为在面试高级开发时,大部分面试官都喜欢问这个类,所以我提前用用大量的篇幅讲解了,在讲解并发包时就不在讲解这个类了,以后的文章会用大量的篇幅去讲解并发包中内容。还有好多网友建议我能不能录几个系列视频,谢谢大家的建议,我接下来也会着手录几个专题视频,关于JDK源码、spring源码,Netty源码等等吧,工作那么多年,自己对各个技术都有所了解吧,我会通过头条号把我在工作中的经历和对技术的研究分享给大家,希望对大家有所帮助吧,因为能力有限,可能有理解不到位或者错误的地方,欢迎交流指正。好多学习Java的同学都把并发包叫做JUC( java.util.concurrent的简称),所以文章出现JUC,就代表并发包。

上一篇文章主要介绍了Set集合的一个非常重要的类HashSet,它的底层是通过HashMap实现的,HashSet的源码是非常容易理解的,本篇文章介绍Set集合的另一个非常重要的实现:TreeSet,它与HashSet的主要区别就是TreeSet是排序的,它支持两种排序:

1: 构造函数 传递了实现Comparator接口的实现类
2:添加的元素实现了Comparable接口
 

我们已经熟悉了HashSet实现原理,那TreeSet是怎样实现的呢?接下来我们就进入TreeSet里面看一看。

本篇文章的主要内容:

1:TreeSet的实例
2:TreeSet的源码解析
3:TreeSet与HashSet的区别
 

一、TreeSet的实例

第一个实例:

public static void main(String[] args) {
 Set<Integer>set = new TreeSet<>();
 set.add(10);
 set.add(1);
 set.add(10);
 set.add(null);
 set.add(2);
 set.add(3);
 set.add(20);
  System .out.println("TreeSet的长度:"+set.size());
 System.out.println("TreeSet遍历:");
 for(Integer i:set){
 System.out.print(i+" ");
 }
}
 

运行结果如图:

TreeSet添加null元素运行结果

第二个实例:

public static void main(String[] args) {
 Set<Integer>set = new TreeSet<>();
 set.add(10);
 set.add(1);
 set.add(10);
 set.add(2);
 set.add(3);
 set.add(20);
 System.out.println("TreeSet的长度:"+set.size());
 System.out.println("TreeSet遍历:");
 for(Integer i:set){
 System.out.print(i+" ");
 }
}
 

TreeSet不添加null元素

第三个实例:

public class Test {
 public static void main(String[] args) {
 Set<TreeSetClass>set = new TreeSet<>();
 set.add(new TreeSetClass(20));
 set.add(new TreeSetClass(2));
 }
}
//TreeSetClass没有实现Comparable接口
class TreeSetClass {
 private Integer pro;
 public TreeSetClass(Integer pro) {
 this.pro = pro;
 }
 public Integer getPro() {
 return pro;
 }
 public void setPro(Integer pro) {
 this.pro = pro;
 }
}
 

运行结果如下:

第四个实例:

public class Test {
 public static void main(String[] args) {
 Set<TreeSetClass>set = new TreeSet<>();
 set.add(new TreeSetClass(20));
 set.add(new TreeSetClass(2));
 }
}
//TreeSetClass实现了Comparable接口,但是 泛型 是Integer
class TreeSetClass implements Comparable<Integer>{
 private Integer pro;
 public TreeSetClass(Integer pro) {
 this.pro = pro;
 }
 public Integer getPro() {
 return pro;
 }
 public void setPro(Integer pro) {
 this.pro = pro;
 }
 @ Override 
 public int compareTo(Integer o) {
 return 0;
 }
}
 

运行结果如下:

第五个实例:

public class Test {
 public static void main(String[] args) {
 Set<TreeSetClass>set = new TreeSet<>();
 set.add(new TreeSetClass(20));
 set.add(new TreeSetClass(2));
 for(TreeSetClass tc:set){
 System.out.println(tc.getPro());
 }
 }
}
//TreeSetClass实现了Comparable接口,泛型是TreeSetClass
class TreeSetClass implements Comparable<TreeSetClass>{
 private Integer pro;
 public TreeSetClass(Integer pro) {
 this.pro = pro;
 }
 public Integer getPro() {
 return pro;
 }
 public void setPro(Integer pro) {
 this.pro = pro;
 }
 @Override
 public int compareTo(TreeSetClass o) {
 if(this.pro>o.pro){
 return 1;
 }else if(this.pro<o.pro){
 return -1;
 }
 return 0;
 }
}
 

运行结果如下:

第六个实例:

public class Test {
 public static void main(String[] args) {
 Set<TreeSetClass>set = new TreeSet<>(new Comparator<TreeSetClass>() {
 @Override
 public int compare(TreeSetClass o1, TreeSetClass o2) {
 if(o1.getPro()>o2.getPro()){
 return 1;
 }else if(o1.getPro()<o2.getPro()){
 return -1;
 }
 return 0;
 }
 });
 set.add(new TreeSetClass(20));
 set.add(new TreeSetClass(2));
 for(TreeSetClass tc:set){
 System.out.println(tc.getPro());
 }
 }
}
class TreeSetClass{
 private Integer pro;
 public TreeSetClass(Integer pro) {
 this.pro = pro;
 }
 public Integer getPro() {
 return pro;
 }
 public void setPro(Integer pro) {
 this.pro = pro;
 }
}
 

运行结果如下:

通过上面的6个实例以及运行结果,大家能不能总结出一些关于TreeSet的特点呢?

特点1:TreeSet不能添加null值,否则会报空指针异常
特点2:如果创建TreeSet时没有传递Comparator,对于自定义的对象必须实现Comparable接口,并且Comparable<T>的泛型必须是自定义的对象。
特点3:如果创建TreeSet时传递了Comparator,则自定义对象不需要实现Comparable接口。
 

上面TreeSet的3个特点非常的重要,大家最好理解掌握,因为TreeSet具有排序功能,所以元素要能够进行排序。接下来我们从源码角度看一下为什么TreeSet元素不能添加null值?为什么传递了Comparator后,自定义对象就无需实现Comparable接口了?为什么创建TreeSet时没有传递Comparator,自定义对象必须实现Comparable接口?带着这3个问题,我们开始今天的源码之旅。在分析源码之前,我们先看一下TreeMap的继承关系,为读懂源码打好基础。

二、TreeSet的源码解析

2.1、TreeSet的成员变量

//底层也是一个Map集合,从上面的图片可以看到NavigableMap就是TreeMap
private transient NavigableMap<E,Object> m;
//因为TreeSet的底层是一个TreeMap,添加的元素作为TreeMap的key,而常量PRESENT是TreeMap的value
private static final Object PRESENT = new Object();
 

从上面的两个成员变量可以看出,TreeSet的实现和HashSet类似,底层都是一个Map集合实现的,添加的元素作为Map集合的key,PRESENT作为Map的value,不过HashSet底层是通过HashMap实现的,而TreeSet由于具有排序功能,它的底层利用TreeMap这个具有排序功能的Map实现的。

2.2、TreeSet的构造函数

//第一个构造函数,没有被public修饰,只能它以及子类调用
TreeSet(NavigableMap<E,Object> m) {
 this.m = m;
}
//第二个构造函数,调用第一个构造函数,将TreeMap赋值给成员变量m
public TreeSet() {
 this(new TreeMap<E,Object>());
}
//第三个构造函数,传递了一个Comparator,然后调用第一个构造函数,然后赋值给成员变量m
public TreeSet(Comparator<? super E> comparator) {
 this(new TreeMap<>(comparator));
}
//第四个构造函数,将Collection添加到TreeSet中
public TreeSet(Collection<? extends E> c) {
 this();
 addAll(c);
}
//第五个构造函数。
public TreeSet(SortedSet<E> s) {
 this(s.comparator());
 addAll(s);
}
 

从上面的构造函数可以看出,初始化成员变量m,也就是TreeMap,上面我总结TreeSet特点时讲到如果创建TreeSet时传递Comparator,则自定义的对象不需要实现Comparable接口。第三个构造函数就是这个作用。

2.3、TreeSet的重要方法

第一个重要的方法add

public boolean add(E e) {
 return m.put(e, PRESENT)==null;
}
 

add方法直接调用了TreeMap的put方法,在 ,我对put方法做了详细的讲解,大家不太熟悉TreeMap的可以点击去观看。

更正 的一个不严谨的地方:

当Comparator!=null时,允许key==null.上面不是说不允许为null吗?
 

如图上面的说法确实有一点问题,并且在这篇文章中的举例也是有问题的,我在这两个方法并没有去比较,直接返回return 0;

//传递Comparator.
public int compareTo(Object o) {
 return 0;
}
 

在这篇文章我举例并不是太严禁,其实做比较在compareTo方法中不能直接返回0,所以大家不要被我误导了。你就记住,不管是TreeMap还是TreeSet,如果key==null,则迭代或者获取时会出现异常。大家对我在TreeMap这一篇文章的举例忽略就行。

所以TreeSet的排序功能是通过TreeMap实现的,而TreeMap底层是利用红黑树实现的,在TreeMap中我详细介绍了怎样排序的,对红黑树不理解的,请看 和 这两篇文章。

其他方法如下:

public boolean remove(Object o) {
 return m.remove(o)==PRESENT;
}
public boolean contains(Object o) {
 return m.containsKey(o);
}
public boolean isEmpty() {
 return m.isEmpty();
}
public int size() {
 return m.size();
}
 

从上面的源码可以看出TreeSet实现机制和HashSet实现机制一样,都是调用底层的Map实现的。前面我对Map做了详细的讲解,这里就不在跟进分析了。

三、TreeSet与HashSet的异同

3.1、相同点

1:都是实现了Set集合
2:元素插入的顺序和取出的顺序不一定相同
3:元素不能重复
 

3.2、不同点

1:TreeSet自定义对象时要么在构造函数传递Comparator,要么自定义对象实现Comparable接口,否则无法比较顺序,会抛出异常。而HashSet没有这个要求。
 

这一篇文章主要介绍了TreeSet的内部实现原理,如果对Map集合非常的熟悉的话,对于TreeSet和HashSet是非常简单的,因为它们都是通过底层的Map集合实现的。

文章来源:智云一二三科技

文章标题:Java集合系列-Set系列-TreeSet

文章地址:https://www.zhihuclub.com/173495.shtml

关于作者: 智云科技

热门文章

网站地图