这一篇文章是集合的最后一篇文章,好多网友私信我能不能写几篇关于并发包中的文章,因为并发包中大部分类我都仔细研究过,也有自己的理解,所以接下来我计划开始对并发包中的类进行源码分析,如果对你有所帮助,希望大家持续关注,集合中还有两个面试过程中问的比较多的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+" ");
}
}
运行结果如图:
第二个实例:
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+" "); } }
第三个实例:
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集合实现的。