【Java入门提高篇】Day31 Java容器类详解(十三)TreeSet详解

简介:   上一篇很水的介绍完了TreeMap,这一篇来看看更水的TreeSet。   本文将从以下几个角度进行展开:   1、TreeSet简介和使用栗子   2、TreeSet源码分析   本篇大约需食用10分钟,各位看官请随意享用。

  上一篇很水的介绍完了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本身也没有太多东西。当然,了解它的内部结构目的是为了更好的使用它。在遇到问题时,每个知识点就是你手中的一张牌,能不能打好这手牌,先要看你这牌好不好,牌不好的话,再聪明也难翻盘。

  

 

 

 

 

 

.  

真正重要的东西,用眼睛是看不见的。
相关文章
|
3月前
|
存储 Java 索引
用Java语言实现一个自定义的ArrayList类
自定义MyArrayList类模拟Java ArrayList核心功能,支持泛型、动态扩容(1.5倍)、增删改查及越界检查,底层用Object数组实现,适合学习动态数组原理。
154 4
|
3月前
|
IDE JavaScript Java
在Java 11中,如何处理被弃用的类或接口?
在Java 11中,如何处理被弃用的类或接口?
235 5
|
3月前
|
XML Java 应用服务中间件
【SpringBoot(一)】Spring的认知、容器功能讲解与自动装配原理的入门,带你熟悉Springboot中基本的注解使用
SpringBoot专栏开篇第一章,讲述认识SpringBoot、Bean容器功能的讲解、自动装配原理的入门,还有其他常用的Springboot注解!如果想要了解SpringBoot,那么就进来看看吧!
501 3
|
3月前
|
JSON 网络协议 安全
【Java】(10)进程与线程的关系、Tread类;讲解基本线程安全、网络编程内容;JSON序列化与反序列化
几乎所有的操作系统都支持进程的概念,进程是处于运行过程中的程序,并且具有一定的独立功能,进程是系统进行资源分配和调度的一个独立单位一般而言,进程包含如下三个特征。独立性动态性并发性。
230 1
|
3月前
|
Java Go 开发工具
【Java】(8)正则表达式的使用与常用类分享
正则表达式定义了字符串的模式。正则表达式并不仅限于某一种语言,但是在每种语言中有细微的差别。
283 1
|
3月前
|
存储 Java 程序员
【Java】(6)全方面带你了解Java里的日期与时间内容,介绍 Calendar、GregorianCalendar、Date类
java.util 包提供了 Date 类来封装当前的日期和时间。Date 类提供两个构造函数来实例化 Date 对象。第一个构造函数使用当前日期和时间来初始化对象。Date( )第二个构造函数接收一个参数,该参数是从1970年1月1日起的毫秒数。
225 1
|
3月前
|
JSON 网络协议 安全
【Java基础】(1)进程与线程的关系、Tread类;讲解基本线程安全、网络编程内容;JSON序列化与反序列化
几乎所有的操作系统都支持进程的概念,进程是处于运行过程中的程序,并且具有一定的独立功能,进程是系统进行资源分配和调度的一个独立单位一般而言,进程包含如下三个特征。独立性动态性并发性。
246 1
|
3月前
|
Java 虚拟化 容器
(Java)Java里JFrame窗体的基本操作(容器布局篇-1)
容器 容器,我的理解是可以包容其他东西的玩意。它可以是一个盒子,可以是一个虚拟化的物品,可只要能包裹住其他存在质体的东西,那么都可以称作是容器。例如:JPanel组件和JScollPane组件两者都是容器也是组件。 既然有容器,那么容器中的布局就必不可少了。不然不规矩的摆放物品,人类看不习惯,我也看不习惯 ???? 本篇内容,将说明java JFrame窗体里容器中几类布局。 说明:所有在JFrame窗体里的容器布局都会使用setLayout()方法,采用的布局参数都将放进这个方法里 绝对布局 调用窗体容器
147 1
|
存储 Java 容器
【Java入门提高篇】Day33 Java容器类详解(十五)PriorityQueue详解
PriorityQueue也是Queue的一个继承者,相比于一般的列表,它的特点便如它的名字一样,出队的时候可以按照优先级进行出队,所以不像LinkedList那样只能按照插入的顺序出队,PriorityQueue是可以根据给定的优先级顺序进行出队的。
1771 0
|
4月前
|
数据采集 存储 弹性计算
高并发Java爬虫的瓶颈分析与动态线程优化方案
高并发Java爬虫的瓶颈分析与动态线程优化方案