【Java入门提高篇】Day31 Java容器类详解(十三)TreeSet详解
上一篇很水的介绍完了TreeMap,这一篇来看看更水的TreeSet。
本文将从以下几个角度进行展开:
1、TreeSet简介和使用栗子
2、TreeSet源码分析
本篇大约需食用10分钟,各位看官请随意享用。
一、TreeSet简介
TreeSet是Set家族中的又一名懒将,跟其他两位一样,与对应的Map关系密不可分
我们先来回顾一下其他两个Set类,HashSet借助于HashMap拥有快速元素插入和查找的特性,LinkedHashSet借助于LinkedHashMap拥有快速插入查找以及使元素保持插入顺序的特性,TreeSet则是借助于TreeMap拥有使内部元素保持有序的特性,当然,所有的Set集合类都有元素去重的特性。当然,要区别一下的是,TreeSet中的有序是指可以按照内部比较器或者外部比较器的顺序对插入的元素进行排序,也就是每次插入后都会调整顺序以保持内部元素整体有序,而LinkedHashSet只能保持元素的插入顺序。
Talk is cheap,show me your code. 嗯,还是来看代码吧:
public class TreeSetTest {
public static void main(String[] args){
TreeSet<String> treeSet = new TreeSet<>();
treeSet.add("Frank");
treeSet.add("Alice");
treeSet.add("Bob");
treeSet.add("Allen");
treeSet.add("Ada");
treeSet.add("Adora");
System.out.println(treeSet);
LinkedHashSet<String> linkedHashSet = new LinkedHashSet<>();
linkedHashSet.add("Frank");
linkedHashSet.add("Alice");
linkedHashSet.add("Bob");
linkedHashSet.add("Allen");
linkedHashSet.add("Ada");
linkedHashSet.add("Adora");
System.out.println(linkedHashSet);
}
}
输出如下:
[Ada, Adora, Alice, Allen, Bob, Frank]
[Frank, Alice, Bob, Allen, Ada, Adora]
可以看到TreeSet给插入的元素自动排序了。那么可不可以放入我们自定义的类元素呢?当然是可以的,不然要它何用
public class TreeSetTest {
public static void main(String[] args){
List<Goods> goods = new ArrayList<>();
goods.add(new Goods("Iphone4S",500.00));
goods.add(new Goods("Iphone5",800.00));
goods.add(new Goods("Iphone6S",2500.00));
goods.add(new Goods("Iphone7S",4500.00));
goods.add(new Goods("Iphone8",6500.00));
goods.add(new Goods("IphoneX",8500.00));
System.out.println(goods);
TreeSet<Goods> treeSet = new TreeSet<>();
treeSet.addAll(goods);
System.out.println(treeSet);
LinkedHashSet<Goods> linkedHashSet = new LinkedHashSet<>();
linkedHashSet.addAll(goods);
System.out.println(linkedHashSet);
}
public static class Goods{
private String name;
private Double price;
public Goods(String name, Double price) {
this.name = name;
this.price = price;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Double getPrice() {
return price;
}
public void setPrice(Double price) {
this.price = price;
}
@Override
public String toString() {
return "Goods{" +
"name='" + name + '\'' +
", price=" + price +
'}';
}
}
}
输出如下:
Exception in thread "main" java.lang.ClassCastException: com.frank.chapter22.TreeSetTest$Goods cannot be cast to java.lang.Comparable
[Goods{name='Iphone4S', price=500.0}, Goods{name='Iphone5', price=800.0}, Goods{name='Iphone6S', price=2500.0}, Goods{name='Iphone7S', price=4500.0}, Goods{name='Iphone8', price=6500.0}, Goods{name='IphoneX', price=8500.0}]
at java.util.TreeMap.compare(TreeMap.java:1294)
at java.util.TreeMap.put(TreeMap.java:538)
at java.util.TreeSet.add(TreeSet.java:255)
at java.util.AbstractCollection.addAll(AbstractCollection.java:344)
at java.util.TreeSet.addAll(TreeSet.java:312)
at com.frank.chapter22.TreeSetTest.main(TreeSetTest.java:25)
欢迎来到大型翻车现场。。。
别慌别慌,问题不大。TreeSet与TreeMap一样,是需要元素实现Comparable接口或者传入一个外部比较器的。为什么String可以不用?看看String的实现吧,人家可是实现了Comparable接口的。
所以有两种方式解决,一种是让Goods类实现Comparable接口,另一种是传入一个外部比较器,我们先来看第一种:
public class TreeSetTest {
public static void main(String[] args){
List<Goods> goods = new ArrayList<>();
goods.add(new Goods("Iphone7S",4500.00));
goods.add(new Goods("Iphone4S",500.00));
goods.add(new Goods("Iphone5",800.00));
goods.add(new Goods("IphoneX",8500.00));
goods.add(new Goods("Iphone6S",2500.00));
goods.add(new Goods("Iphone8",6500.00));
goods.add(new Goods("Iphone8",6500.00));
System.out.println(goods);
TreeSet<Goods> treeSet = new TreeSet<>();
treeSet.addAll(goods);
System.out.println(treeSet);
LinkedHashSet<Goods> linkedHashSet = new LinkedHashSet<>();
linkedHashSet.addAll(goods);
System.out.println(linkedHashSet);
}
public static class Goods implements Comparable{
private String name;
private Double price;
public Goods(String name, Double price) {
this.name = name;
this.price = price;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Double getPrice() {
return price;
}
public void setPrice(Double price) {
this.price = price;
}
@Override
public String toString() {
return "Goods{" +
"name='" + name + '\'' +
", price=" + price +
'}';
}
@Override
public int compareTo(Object o) {
if (!(o instanceof Goods)){
return -1;
}
Goods goods = (Goods) o;
return this.price.compareTo(goods.getPrice());
}
}
}
为了看出效果,把几个商品的顺序调整了一下,输出如下:
[Goods{name='Iphone7S', price=4500.0}, Goods{name='Iphone4S', price=500.0}, Goods{name='Iphone5', price=800.0}, Goods{name='IphoneX', price=8500.0}, Goods{name='Iphone6S', price=2500.0}, Goods{name='Iphone8', price=6500.0}, Goods{name='Iphone8', price=6500.0}]
[Goods{name='Iphone4S', price=500.0}, Goods{name='Iphone5', price=800.0}, Goods{name='Iphone6S', price=2500.0}, Goods{name='Iphone7S', price=4500.0}, Goods{name='Iphone8', price=6500.0}, Goods{name='IphoneX', price=8500.0}]
[Goods{name='Iphone7S', price=4500.0}, Goods{name='Iphone4S', price=500.0}, Goods{name='Iphone5', price=800.0}, Goods{name='IphoneX', price=8500.0}, Goods{name='Iphone6S', price=2500.0}, Goods{name='Iphone8', price=6500.0}, Goods{name='Iphone8', price=6500.0}]
这里我们按价格进行了升序排列,接下来使用外部比较器的方式进行价格的倒序排列:
public class TreeSetTest {
public static void main(String[] args){
List<Goods> goods = new ArrayList<>();
goods.add(new Goods("Iphone7S",4500.00));
goods.add(new Goods("Iphone4S",500.00));
goods.add(new Goods("Iphone5",800.00));
goods.add(new Goods("IphoneX",8500.00));
goods.add(new Goods("Iphone6S",2500.00));
goods.add(new Goods("Iphone8",6500.00));
goods.add(new Goods("Iphone8",6500.00));
System.out.println(goods);
// 1、使用Lamada表达式
//TreeSet<Goods> treeSet = new TreeSet<>((g1,g2) -> g2.getPrice().compareTo(g1.getPrice()));
// 2、使用匿名内部类
TreeSet<Goods> treeSet = new TreeSet<>(new Comparator<Goods>() {
@Override
public int compare(Goods o1, Goods o2) {
return o2.getPrice().compareTo(o1.getPrice());
}
});
treeSet.addAll(goods);
System.out.println(treeSet);
LinkedHashSet<Goods> linkedHashSet = new LinkedHashSet<>();
linkedHashSet.addAll(goods);
System.out.println(linkedHashSet);
}
public static class Goods{
private String name;
private Double price;
public Goods(String name, Double price) {
this.name = name;
this.price = price;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Double getPrice() {
return price;
}
public void setPrice(Double price) {
this.price = price;
}
@Override
public String toString() {
return "Goods{" +
"name='" + name + '\'' +
", price=" + price +
'}';
}
}
}
在传入外部比较器的时候,也有很多种姿势,这里还是选了匿名内部类的方式进行传入,因为这里只需要使用一次,Lamada表达式还没有做介绍,这里就先不讲了,欣赏一下就好,先领略一下它的强大。
[Goods{name='Iphone7S', price=4500.0}, Goods{name='Iphone4S', price=500.0}, Goods{name='Iphone5', price=800.0}, Goods{name='IphoneX', price=8500.0}, Goods{name='Iphone6S', price=2500.0}, Goods{name='Iphone8', price=6500.0}, Goods{name='Iphone8', price=6500.0}]
[Goods{name='IphoneX', price=8500.0}, Goods{name='Iphone8', price=6500.0}, Goods{name='Iphone7S', price=4500.0}, Goods{name='Iphone6S', price=2500.0}, Goods{name='Iphone5', price=800.0}, Goods{name='Iphone4S', price=500.0}]
[Goods{name='Iphone7S', price=4500.0}, Goods{name='Iphone4S', price=500.0}, Goods{name='Iphone5', price=800.0}, Goods{name='IphoneX', price=8500.0}, Goods{name='Iphone6S', price=2500.0}, Goods{name='Iphone8', price=6500.0}, Goods{name='Iphone8', price=6500.0}]
这样,就完成了倒序排列了,很简单吧。
二、TreeSet源码分析
先来看看TreeSet的继承关系图:

跟TreeMap的继承机构差不多,NavigableSet接口中存在大量的导航方法,可以帮助更快定位想要查找的元素,AbstractSet提供Set的部分默认实现,这样只需要实现其它方法即可。

可以看到,TreeSet中的方法并不是很多,除了导航方法之外,就是几个最常用的方法了,如add,addAll,remove,contains。接下来让我们一起看看这几个方法是如何实现的:
先来看看内部成员和构造函数:
/**
* 内部默默无闻工作的Map
*/
private transient NavigableMap<E,Object> m; // map中共用的一个value
private static final Object PRESENT = new Object();
//默认构造方法,根据其元素的自然顺序进行排序
public TreeSet() {
this(new TreeMap<E,Object>());
} //构造一个包含指定 collection 元素的新 TreeSet,它按照其元素的自然顺序进行排序。
public TreeSet(Comparator<? super E> comparator) {
this(new TreeMap<>(comparator));
} //构造一个新的空 TreeSet,它根据指定比较器进行排序。
public TreeSet(Collection<? extends E> c) {
this();
addAll(c);
} //构造一个与指定有序 set 具有相同映射关系和相同排序的新 TreeSet。
public TreeSet(SortedSet<E> s) {
this(s.comparator());
addAll(s);
} TreeSet(NavigableMap<E,Object> m) {
this.m = m;
}
add方法,嗯,够简明扼要。
public boolean add(E e) {
return m.put(e, PRESENT)==null;
}
addAll:
public boolean addAll(Collection<? extends E> c) {
// 先检测集合是否继承了SortedSet接口,内部map是否为TreeMap
// 并且检测使用的比较器是否与内部Map的比较器一致
// 如果一致的话则使用TreeMap的addAllForTreeSet方法进行批量插入
// addAllForTreeSet方法可以在常量时间对有序元素进行插入
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;
Comparator<?> cc = set.comparator();
Comparator<? super E> mc = map.comparator();
if (cc==mc || (cc != null && cc.equals(mc))) {
map.addAllForTreeSet(set, PRESENT);
return true;
}
}
// 如果不满足条件,则使用父类的addAll方法进行添加
return super.addAll(c);
}
嗯,这里会先激进优化,不行再用笨办法一个个添加,因为如果是将大量元素插入TreeMap中相对而言还是比较耗时耗力的,每次插入一个元素都可能导致整体结构的调整,而如果插入的元素刚好是有序的,那么就可以对这个过程进行一次很不错的优化。
public boolean remove(Object o) {
return m.remove(o)==PRESENT;
}
public boolean contains(Object o) {
return m.containsKey(o);
}
remove和contains方法也很简单。而且还带一点粗暴
到此,本篇就结束了,其实也没太多内容,因为TreeSet本身也没有太多东西。当然,了解它的内部结构目的是为了更好的使用它。在遇到问题时,每个知识点就是你手中的一张牌,能不能打好这手牌,先要看你这牌好不好,牌不好的话,再聪明也难翻盘。

.
【Java入门提高篇】Day31 Java容器类详解(十三)TreeSet详解的更多相关文章
- 【Java入门提高篇】Java集合类详解(一)
今天来看看Java里的一个大家伙,那就是集合. 集合嘛,就跟它的名字那样,是一群人多势众的家伙,如果你学过高数,没错,就跟里面说的集合是一个概念,就是一堆对象的集合体.集合就是用来存放和管理其他类对象 ...
- 【Java入门提高篇】Day32 Java容器类详解(十四)ArrayDeque详解
今天来介绍一个不太常见也不太常用的类——ArrayDeque,这是一个很不错的容器类,如果对它还不了解的话,那么就好好看看这篇文章吧. 看完本篇,你将会了解到: 1.ArrayDeque是什么? 2. ...
- 【Java入门提高篇】Day28 Java容器类详解(十)LinkedHashMap详解
今天来介绍一下容器类中的另一个哈希表———>LinkedHashMap.这是HashMap的关门弟子,直接继承了HashMap的衣钵,所以拥有HashMap的全部特性,并青出于蓝而胜于蓝,有着一 ...
- 【Java入门提高篇】Day26 Java容器类详解(八)HashSet源码分析
前面花了好几篇的篇幅把HashMap里里外外说了个遍,大家可能对于源码分析篇已经讳莫如深了.别慌别慌,这一篇来说说集合框架里最偷懒的一个家伙——HashSet,为什么说它是最偷懒的呢,先留个悬念,看完 ...
- 【Java入门提高篇】Day21 Java容器类详解(四)ArrayList源码分析
今天要介绍的是List接口中最常用的实现类——ArrayList,本篇的源码分析基于JDK8,如果有不一致的地方,可先切换到JDK8后再进行操作. 本篇的内容主要包括这几块: 1.源码结构介绍 2.源 ...
- 【Java入门提高篇】Day20 Java容器类详解(三)List接口
今天要说的是Collection族长下的三名大将之一,List,Set,Queue中的List,它们都继承自Collection接口,所以Collection接口的所有操作,它们自然也是有的. Lis ...
- 【Java入门提高篇】Day27 Java容器类详解(九)LinkedList详解
这次介绍一下List接口的另一个践行者——LinkedList,这是一位集诸多技能于一身的List接口践行者,可谓十八般武艺,样样精通,栈.队列.双端队列.链表.双向链表都可以用它来模拟,话不多说,赶 ...
- 【Java入门提高篇】Day19 Java容器类详解(二)Map接口
上一篇里介绍了容器家族里的大族长——Collection接口,今天来看看容器家族里的二族长——Map接口. Map也是容器家族的一个大分支,但里面的元素都是以键值对(key-value)的形式存放的, ...
- 【Java入门提高篇】Day1 抽象类
基础部分内容差不多讲解完了,今天开始进入Java提高篇部分,这部分内容会比之前的内容复杂很多,希望大家做好心理准备,看不懂的部分可以多看两遍,仍不理解的部分那一定是我讲的不够生动,记得留言提醒我. 好 ...
随机推荐
- 前后端分离开发之前端自己的API(DB)---- (1)
Creating demo APIs for Front-End Developer 心理准备 Tool-1 开发工具/编辑器:Visual Studio Code , 即 VSCode官网: htt ...
- 内存管理cpuset,mempolicy[原理]
介绍cpuset,mbind,set_mempolicy在内存管理上的应用 change log :确定先从mempolicy的man 手册翻译开始研究,计划如下 .先从man手册入手,通过实现mem ...
- python unittest单元测试
unittest单元测试框架:包含测试用例编写.测试收集\测试用例加载.执行测试用例.生成测试用例报告,同时,更提供了添加断言,异常处理等. 第一:创建测试类,创建测试用例 第二:收集测试用例,加载测 ...
- [视频]K8飞刀 一键免杀 IE神洞网马教程
[视频]K8飞刀 一键免杀 IE神洞网马教程 https://pan.baidu.com/s/16ZrTs
- 可能比文档还详细--VueRouter完全指北
可能比文档还详细--VueRouter完全指北 前言 关于标题,应该算不上是标题党,因为内容真的很多很长很全面.主要是在官网的基础上又详细总结,举例了很多东西.确保所有新人都能理解!所以实际上很多东西 ...
- Python3学习笔记 - day1
前言 本文不是一篇系统的从零开始学习Python的教程,如果你需要从零开始学习Python,廖雪峰的官方网站中Python教程这部分将是比较好的一种选择,如果你英语比较好,也可以在国外的一些网站上找到 ...
- [每天解决一问题系列 - 0008] 关于.net framework 路径最大长度的问题
问题描述: 有时候,在copy文件的时候,会提示目录长度太大,无法copy 解决方法: 可以使用Long Path Tool 解决问题 相关解释: http://blogs.msdn.com/b/bc ...
- Python内置类型(3)——比较
python有8种比较运算符:<.<=.>.>=.==.!=.is.is not,它们具有相同的优先级.它们具体的含义如下: 运算符 含义 < 小于 <= 小于或等 ...
- Apache Commons Beanutils 一 (使用PropertyUtils访问Bean属性)
BeanUtils简要描述 beanutils,顾名思义,是java bean的一个工具类,可以帮助我们方便的读取(get)和设置(set)bean属性值.动态定义和访问bean属性: 细心的话,会发 ...
- ajaxFileUpload带参数提交(亲测可用)
使用ajaxFileUpload上传文件时,有时需要带参数提交,网上有很多资料说使用data,但其实要使用data带参数是需要修改的,否则后台是获取不到的. 分析原因: ajaxFileUpload为 ...