【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提高篇部分,这部分内容会比之前的内容复杂很多,希望大家做好心理准备,看不懂的部分可以多看两遍,仍不理解的部分那一定是我讲的不够生动,记得留言提醒我. 好 ... 
随机推荐
- Django Rest Framework-介绍
			什么是RESTful REST与技术无关,代表的是一种软件架构风格,REST是Representational State Transfer的简称,中文翻译为"表征状态转移" RE ... 
- 关于resin的illegal utf8 encoding at (190)解决方式
			最近在项目开发中,出现了编码异常,内容如下:- illegal utf8 encoding at (190)com.caucho.jsp.JspParseException: illegal utf8 ... 
- walle多渠道打包+Tinker(bugly)热更新集成+360加固(乐固)
			这三个东东是干啥的相信大家都有所耳闻了,如果你没有听说过,请出门左拐,百度一下你就知道.这里不对这三个东东具体的集成方式做详细的介绍,因为官方文档已经写的很详细了,主要是对同时使用这三个东东时所需要注 ... 
- 图像处理池化层pooling和卷积核
			1.池化层的作用 在卷积神经网络中,卷积层之间往往会加上一个池化层.池化层可以非常有效地缩小参数矩阵的尺寸,从而减少最后全连层中的参数数量.使用池化层即可以加快计算速度也有防止过拟合的作用. 2.为什 ... 
- Java并发编程笔记之ArrayBlockingQueue源码分析
			JDK 中基于数组的阻塞队列 ArrayBlockingQueue 原理剖析,ArrayBlockingQueue 内部如何基于一把独占锁以及对应的两个条件变量实现出入队操作的线程安全? 首先我们先大 ... 
- spring AOP 之二:@AspectJ注解的3种配置
			@AspectJ相关文章 <spring AOP 之二:@AspectJ注解的3种配置> <spring AOP 之三:使用@AspectJ定义切入点> <spring ... 
- Java设计模式学习记录-组合模式
			前言 今天要介绍的设计模式是组合模式,组合模式也是结构型设计模式的一种,它主要体现了整体与部分的关系,其典型的应用就是树形结构.组合是一组对象,其中的对象可能包含一个其他对象,也可能包含一组其他对象. ... 
- netty源码解解析(4.0)-9 ChannelPipleline的默认实现-链表管理
			io.netty.channel.DefaultChannelPipeline implements ChannelPipleline DefaultChannelPiple给出了ChannelP ... 
- JavaScript之八皇后问题(递归)
			八皇后问题,是一个古老而著名的问题,该问题最早由国际西洋棋棋手马克斯·贝瑟尔(Max Bezzel)于1848年提出.八皇后问题的具体描述为:在\(8\times8\)的国际象棋上摆放8个皇后,使 ... 
- 基本css说明
			/*-moz代表firefox浏览器私有属性 2.-ms代表ie浏览器私有属性 3.-webkit代表safari.chrome私有属性*/ body, dl, dd, ul, ol, h1, h2, ... 
