TreeSet源码详解

简介: TreeSet源码详解

一、介绍

在上期文章中,我们从源码层面详细分析了java集合框架中Set一族的实现HashSet,它的内部维护了一个HashMap对象作为内部存储,也就是说HashSet的底层结构为哈希表,今天我们介绍Set的另一个实现——TreeSet,对标HashSet与HashMap的关系,我们猜想TreeSet可能会维护一个TreeMap作为内部存储,事实也确实如此,因此,TreeSet的特性均继承于TreeMap,如元素有序等。在学习TreeSet源码之前,必须对TreeMap的源码了如指掌。由于TreeMap底层实现为较复杂的红黑树,对TreeMap源码不了解的同学请一定要参考前面的文章TreeMap源码。下面我们先看一下TreeSet的UML图。

UML图.png

二、类的声明

public class TreeSet<E> extends AbstractSet<E>
                        implements NavigableSet<E>, Cloneable, java.io.Serializable

从类的声明和上面的UML类图中可以了解到:

  • 继承AbstractSet抽象类,对其进行了扩展。
  • 实现了NavigableSet接口,表示它是一个提供导航功能的Set集合,满足Set集合的定义
  • 实现了Cloneable接口,提供了对象克隆方法,但请注意,是浅克隆
  • 实现了Serializable接口,支持序列化

三、成员变量

// HashSet的底层使用NavigableMap对象作为存储结构,
// 目前我们常用的NavigableMap实现为TreeMap
// 而TreeMap已经实现了红黑树,因此TreeSet无需再次对红黑树进行实现,直接通过TreeMap对红黑树进行数据的存取即可。
private transient NavigableMap<E,Object> m;

// 虽然TreeSet使用TreeMap来操作红黑树,
// 但是TreeMap存储的是<key,value>键值对,
// 因此TreeSet在保存数据时,key为实际保存的数据,使用一个固定的对象PRESENT作为假的value值
private static final Object PRESENT = new Object();

四、构造函数

TreeSet提供了四个构造函数,由于TreeSet的底层为TreeMap,因此在TreeSet的构造方法中,都是先实例化一个TreeMap对象。

在TreeSet的众多构造函数中,有一个比较特殊的构造函数

TreeSet(NavigableMap<E,Object> m) {
   
   
    this.m = m;
}

该构造函数的访问修饰符为默认,因此只能被类内部和同包内的子类调用。通过接收一个NavigableMap对象,并将其作为内部的NavigableMap对象维护,比如TreeMap。

  • 无参构造

    通过TreeMap的无参构造,实例化一个TreeMap对象,作为TreeSet的内部成员变量。

    public TreeSet() {
         
         
        this(new TreeMap<E,Object>());
    }
    
  • 指定比较器Comparator

    其实还是调用TreeMap的构造方法public TreeMap(Comparator<? super K> comparator)来实例化一个TreeMap对象,作为TreeSet的内部成员变量。

    public TreeSet(Comparator<? super E> comparator) {
         
         
        this(new TreeMap<>(comparator));
    }
    
  • 通过传入一个集合构造

    先通过无参构造,实例化TreeSet对象,然后将集合中的元素通过addAll()方法批量保存到TreeSet对象中。

    其中addAll()方法的实现与TreeMap中putAll()方法的实现几乎完全相同,故不再赘述,可查看前面的文章TreeMap

    public TreeSet(Collection<? extends E> c) {
         
         
        this();
        addAll(c);
    }
    
    public  boolean addAll(Collection<? extends E> c) {
         
         
        // Use linear-time version if applicable
        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;
            }
        }
        return super.addAll(c);
    }
    
  • 通过传入一个有序集合构造

    在有序集合SortedSet中,提供给我们一个方法comparator()来获取有序集合中的比较器,再根据这个比较器来实例化一个TreeSet对象。如果有序集合中不存在比较器Comparator,则从TreeMap中我们也知道,键值对中的key必须实现Compareable接口中的compareTo()方法。

    其中addAll()方法的实现与TreeMap中putAll()方法的实现几乎完全相同,故不再赘述,可查看前面的文章TreeMap

    public TreeSet(SortedSet<E> s) {
         
         
        this(s.comparator());
        addAll(s);
    }
    


五、常用方法

由于TreeSet实现NavigableSet接口,NavigableSet接口又继承于SortedSet接口。而TreeMap实现NavigableMap接口,NavigableMap接口又继承于SortedMap接口,如下图所示:

TreeSet与TreeMap的接口实现关系比较.png

二者相似度极高,因此我们可以断定

  • SortedSet接口的方法都是通过调用TreeMap中实现SortedMap接口方法而实现的。

  • NavigableSet接口的方法都是通过调用TreeMap中实现NavigableMap接口方法而实现的。

下面我们通过源码进行分析:

1. NavigableSet接口的实现

  • lower()

    获取比指定元素小的元素中最大的一个元素。调用内部TreeMap对象实现NavigableMap接口的lowerKey()方法,

    public E lower(E e) {
         
         
        return m.lowerKey(e);
    }
    
  • floor()

    获取小于等于指定元素的元素中最大的一个元素。调用内部TreeMap对象实现NavigableMap接口的floorKey()方法,

    public E floor(E e) {
         
         
        return m.floorKey(e);
    }
    
  • ceiling()

    获取大于等于指定元素的元素中最小的一个元素。调用内部TreeMap对象实现NavigableMap接口的ceilingKey()方法,

    public E ceiling(E e) {
         
         
        return m.ceilingKey(e);
    }
    
  • higher()

    获取比指定元素大的元素中最小的一个元素。调用内部TreeMap对象实现NavigableMap接口的higherKey()方法,

    public E higher(E e) {
         
         
        return m.higherKey(e);
    }
    
  • pollFirst()

    获取set集合中第一个元素,并将其删除。调用内部TreeMap对象实现NavigableMap接口的pollFirstEntry()方法,获取首个键值对节点并删除后,返回该键值对节点中的key。

    public E pollFirst() {
         
         
        Map.Entry<E,?> e = m.pollFirstEntry();
        return (e == null) ? null : e.getKey();
    }
    
  • pollLast()

    获取set集合中最后一个元素,并将其删除。调用内部TreeMap对象实现NavigableMap接口的pollLastEntry()方法,获取最后一个键值对节点并删除后,返回该键值对节点中的key。

    public E pollLast() {
         
         
        Map.Entry<E,?> e = m.pollLastEntry();
        return (e == null) ? null : e.getKey();
    }
    

2. SortedSet接口的实现

  • comparator()

    获取当前实例中的比较器

    public Comparator<? super E> comparator() {
         
         
        return m.comparator();
    }
    
  • first()

    获取set集合中第一个元素。调用内部TreeMap对象实现SortedMap接口的firstKey()方法,获取首个键值对节点中的key。

    public E first() {
         
         
        return m.firstKey();
    }
    
  • last()

    获取set集合中最后一个元素。调用内部TreeMap对象实现SortedMap接口的lastKey()方法,获取最后一个键值对节点中的key。

    public E last() {
         
         
        return m.lastKey();
    }
    

六、总结

  • 元素有序,基于比较器或Compareable接口的compareTo()方法实现元素的排序
  • 线程不安全
  • TreeSet的底层实现为TreeMap,TreeMap的底层实现为红黑树,因此TreeSet的底层实现为红黑树,TreeSet通过调用TreeMap的方法对红黑树进行操作。




纸上得来终觉浅,绝知此事要躬行。

————————————————我是万万岁,我们下期再见————————————————

相关文章
|
存储 缓存 关系型数据库
⑩⑧【MySQL】InnoDB架构、事务原理、MVCC多版本并发控制
⑩⑧【MySQL】InnoDB架构、事务原理、MVCC多版本并发控制
408 0
|
5月前
|
数据可视化 算法 物联网
室内电子地图开发流程解析:从技术选型到避坑实战
本文旨在解析室内电子地图制作的核心技术,解决如何在复杂室内环境中高效、精准地完成空间数据建模与多源数据融合,从而提升导航效率、优化用户体验的问题。如需室内电子地图解决方案欢迎私信获取,如有项目合作及技术交流欢迎私信作者。
259 2
|
5月前
|
安全 Java 程序员
final 关键字的常见用法及相关注意事项
本文深入解析Java中的`final`关键字,涵盖其基础概念、具体用法及高级应用场景。`final`可修饰变量、方法和类,分别实现常量定义、方法不可重写和类不可继承的功能。文章通过实例讲解了`final`在创建不可变对象、优化性能等方面的作用,并提醒开发者注意使用时的限制与权衡。掌握`final`的正确用法,有助于提升代码的安全性、性能和可维护性,是Java程序员进阶必备知识。文末还提供了相关面试资料供读者学习参考。
242 2
|
8月前
|
存储 缓存 安全
Java字符串缓冲区
字符串缓冲区是用于处理可变字符串的容器,Java中提供了`StringBuffer`和`StringBuilder`两种实现。由于`String`类不可变,当需要频繁修改字符串时,使用缓冲区更高效。`StringBuffer`是一个线程安全的容器,支持动态扩展、任意类型数据转为字符串存储,并提供多种操作方法(如`append`、`insert`、`delete`等)。通过这些方法,可以方便地对字符串进行添加、插入、删除等操作,最终将结果转换为字符串。示例代码展示了如何创建缓冲区对象并调用相关方法完成字符串操作。
233 13
|
JavaScript 定位技术 API
Vue获取照片拍摄的地理位置信息
iPhone屏幕尺寸和开发适配
774 155
|
消息中间件 存储 Java
服务重启了,如何保证线程池中的数据不丢失?
【8月更文挑战第30天】为确保服务重启时线程池数据不丢失,可采用数据持久化(如数据库或文件存储)、使用可靠的任务队列(如消息队列或分布式任务队列系统)、状态监测与恢复机制,以及分布式锁等方式。这些方法能有效提高系统稳定性和可靠性,需根据具体需求选择合适方案并进行测试优化。
867 5
|
机器学习/深度学习 数据挖掘 数据处理
Pandas库
Pandas库是Python中进行数据分析和处理的强大工具,通过其丰富的功能和简洁的API,可以高效地完成各种数据处理任务,为后续的数据分析和机器学习提供了有力的支持。
385 63
|
存储 分布式计算 大数据
【大数据】分布式文件系统HDFS
【大数据】分布式文件系统HDFS
476 0
【大数据】分布式文件系统HDFS
|
安全 前端开发 API
ThinkPHP5 API模块开发规范与示例
【7月更文挑战第6天】本技术文档旨在指导开发者如何完全遵循ThinkPHP5框架的开发规范来构建RESTful API模块。ThinkPHP5(简称TP5)是一款基于PHP的轻量级MVC框架,其简洁、高效的特点非常适合快速开发Web应用及API接口。以下是创建API模块的基本步骤、最佳实践以及代码示例。
678 0
|
分布式计算 Hadoop 大数据
大数据处理框架在零售业的应用:Apache Hadoop与Apache Spark
【8月更文挑战第20天】Apache Hadoop和Apache Spark为处理海量零售户数据提供了强大的支持
292 0