TreeSet 深度解析

在 Java 集合框架中,TreeSet基于红黑树实现的有序、不重复集合,它不仅能保证元素无重复,还能按照指定规则对元素进行排序,是处理有序唯一元素场景的核心工具。

一、TreeSet 基础定义

TreeSetjava.util 包下的实现类,核心特性

  1. 元素唯一性:不允许存储重复元素(通过比较器判断重复,而非 equals 方法);
  2. 有序性 :元素会按照自然排序 (如 Integer 升序、String 字典序)或自定义排序规则排列;
  3. 非线程安全 :多线程并发操作时需手动加锁,或使用 Collections.synchronizedSortedSet 包装;
  4. 不允许 null 元素 :JDK 8 及以后,TreeSet 存储 null 会抛出 NullPointerException(排序时无法比较 null);
  5. 底层数据结构红黑树(自平衡二叉查找树),保证增删改查的时间复杂度为 O(logn)。

二、TreeSet 继承与实现关系

TreeSet 的继承体系清晰体现了其功能定位,完整层级关系如下:

html 复制代码
java.lang.Object
    ↳ java.util.AbstractCollection<E>
        ↳ java.util.AbstractSet<E>
            ↳ java.util.TreeSet<E>

核心接口实现

  1. NavigableSet<E>:提供导航性方法(如获取小于 / 大于某个元素的节点、逆序遍历等);
  2. SortedSet<E>:保证集合元素有序,支持获取子集、首尾元素等有序操作;
  3. Cloneable:支持克隆;
  4. Serializable:支持序列化。

简化理解 :TreeSet = 有序 + 无重复 + 导航功能的集合,所有有序特性依赖 NavigableSetSortedSet 接口。

三、TreeSet 源码深度解析(JDK 8)

TreeSet 是装饰者模式 的典型应用,底层没有自己的数据结构 ,完全基于 TreeMap 实现(所有核心操作都委托给 TreeMap)。这是理解 TreeSet 源码的核心突破口。

1. 核心成员变量

java 复制代码
public class TreeSet<E> extends AbstractSet<E>
    implements NavigableSet<E>, Cloneable, java.io.Serializable
{
    // 底层存储:TreeSet 的所有元素都存在 TreeMap 的 key 中
    private transient NavigableMap<E,Object> m;

    // 固定的默认值:TreeMap 的 value 是无意义的占位符,所有元素共享此对象
    private static final Object PRESENT = new Object();

    // 序列化版本号
    private static final long serialVersionUID = -2479143000642829578L;
}

结论

  • TreeSet 本质是TreeMap 的 key 集合 ,value 统一为 PRESENT 静态对象;
  • TreeMap 底层是红黑树,因此 TreeSet 天然继承了红黑树的有序、自平衡特性。

2. 核心构造方法

TreeSet 提供 5 种构造方法,核心都是初始化底层的 TreeMap:

java 复制代码
// 1. 无参构造:使用自然排序,底层创建 TreeMap
public TreeSet() {
    this(new TreeMap<E,Object>());
}

// 2. 带比较器构造:自定义排序规则
public TreeSet(Comparator<? super E> comparator) {
    this(new TreeMap<>(comparator));
}

// 3. 集合参数构造:将指定集合转为 TreeSet,自然排序
public TreeSet(Collection<? extends E> c) {
    this();
    addAll(c);
}

// 4. SortedSet参数构造:复用已有排序规则
public TreeSet(SortedSet<E> s) {
    this(s.comparator());
    addAll(s);
}

// 5. 包访问权限构造:直接传入 NavigableMap(供内部使用)
TreeSet(NavigableMap<E,Object> m) {
    this.m = m;
}

核心逻辑 :所有构造方法最终都是为了初始化底层的 NavigableMap(实际为 TreeMap)。

3. 核心增删改查方法

TreeSet 的所有方法都是调用 TreeMap 的对应方法,源码极其简洁:

(1)add (E e):添加元素
java 复制代码
public boolean add(E e) {
    // 调用 TreeMap 的 put 方法,key=元素,value=固定的 PRESENT
    return m.put(e, PRESENT) == null;
}

原理

  • TreeMap 的 put 会将元素插入红黑树,并自动平衡树结构;
  • 若元素已存在(比较器判定重复),put 返回旧值,add 返回 false(保证元素唯一);
  • 若元素不存在,put 返回 null,add 返回 true(插入成功)。
(2)remove (Object o):删除元素
java 复制代码
public boolean remove(Object o) {
    // 调用 TreeMap 的 remove 方法,返回旧值
    return m.remove(o) == PRESENT;
}

原理:删除红黑树中的节点,自动调整树平衡;若元素存在,删除成功返回 true,否则返回 false。

(3)contains (Object o):判断元素是否存在
java 复制代码
public boolean contains(Object o) {
    // 调用 TreeMap 的 containsKey
    return m.containsKey(o);
}
(4)size()/isEmpty()/clear()
java 复制代码
public int size() { return m.size(); }
public boolean isEmpty() { return m.isEmpty(); }
public void clear() { m.clear(); }

全部委托 TreeMap 实现,无任何自定义逻辑。

4. 排序核心:比较器

TreeSet 判断元素是否重复排序规则 ,完全依赖比较器,而非 equals/hashCode:

  1. 自然排序 :元素实现 Comparable 接口,重写 compareTo 方法(如 Integer、String 默认实现);
  2. 自定义排序 :创建 TreeSet 时传入 Comparator 接口实现,重写 compare 方法。

源码关联:TreeMap 的 put/get 操作会调用比较器的 compare 方法,若返回 0,判定为重复元素。

四、TreeSet 内部方法(有序 / 导航方法)

作为 NavigableSet 实现类,TreeSet 提供了大量有序导航方法,这是 HashSet 不具备的核心能力:

  1. first():获取集合第一个元素(最小元素);
  2. last():获取集合最后一个元素(最大元素);
  3. lower(E e):获取小于 e 的最大元素;
  4. higher(E e):获取大于 e 的最小元素;
  5. floor(E e):获取小于等于 e 的最大元素;
  6. ceiling(E e):获取大于等于 e 的最小元素;
  7. subSet(E from, boolean fromInclusive, E to, boolean toInclusive):获取指定范围的子集;
  8. headSet(E to, boolean inclusive):获取小于 to 的子集;
  9. tailSet(E from, boolean inclusive):获取大于 from 的子集。

这些方法底层均调用 TreeMap 的对应导航方法,时间复杂度 O(logn)。

五、TreeSet 迭代器实现

TreeSet 的迭代器基于红黑树的中序遍历实现,保证遍历顺序与排序顺序一致。

1. 获取迭代器

java 复制代码
public Iterator<E> iterator() {
    // 调用 TreeMap 的 keySet 迭代器
    return m.navigableKeySet().iterator();
}

2. 迭代器特性

  1. 有序遍历 :中序遍历红黑树,输出升序排序结果(自然排序 / 自定义排序);
  2. 快速失败(Fail-Fast) :迭代过程中若修改集合(add/remove),会抛出 ConcurrentModificationException
  3. 支持逆序迭代 :通过 descendingIterator() 获取逆序迭代器,遍历降序元素。

核心原理:红黑树的中序遍历规则 = 左子树 → 根节点 → 右子树,天然保证有序输出。

六、TreeSet 与 HashSet 核心对比

两者都是 Set 接口实现类,核心区别源于底层数据结构,详细对比:

特性 TreeSet HashSet
底层结构 红黑树 哈希表(数组 + 链表 + 红黑树)
元素顺序 有序(自然 / 自定义排序) 无序(JDK 8 后遍历顺序固定,但无排序意义)
重复判断 比较器(compare/compareTo 返回 0) equals() + hashCode()
元素要求 必须实现 Comparable / 传入比较器 无强制要求(建议重写 equals/hashCode)
时间复杂度 增删改查:O(logn) 增删改查:O(1)(哈希冲突少)
null 元素 不允许 允许存储 1 个 null
性能 较低(红黑树自平衡开销) 较高(哈希寻址效率高)
适用场景 需排序、去重的有序场景 仅需去重、追求高性能场景

一句话总结:需要有序选 TreeSet,追求性能选 HashSet。

七、TreeSet 面试高频知识点

1. TreeSet 如何保证元素有序?

底层基于红黑树实现,插入元素时通过比较器(Comparable/Comparator) 确定元素在红黑树中的位置,迭代时通过中序遍历红黑树,保证输出有序。

2. TreeSet 如何判断元素重复?

不是通过 equals 方法 ,而是通过比较器的 compareTocompare 方法:若返回 0,判定为重复元素,拒绝插入。

3. 为什么 TreeSet 不允许存储 null?

TreeSet 插入元素时会调用比较器进行排序,null 无法参与比较,JDK 8 及以后会直接抛出 NullPointerException

4. TreeSet 与 TreeMap 的关系?

TreeSet 是 TreeMap 的包装类,TreeSet 所有元素存储在 TreeMap 的 key 中,value 为固定静态对象,所有核心操作委托 TreeMap 实现。

5. TreeSet 迭代时为什么会报并发修改异常?

TreeSet 迭代器采用快速失败机制 ,迭代过程中会检测 modCount(修改次数),若集合被修改,modCount 变化,立即抛出异常,避免遍历数据不一致。

6. 自然排序 vs 自定义排序优先级?

自定义排序(Comparator)优先级高于自然排序(Comparable):若创建 TreeSet 时传入 Comparator,优先使用自定义比较器,否则使用元素的 Comparable 接口。

7. TreeSet 是线程安全的吗?

不是。多线程环境下,推荐使用 java.util.concurrent.ConcurrentSkipListSet(线程安全的有序集合),或用 Collections.synchronizedSortedSet 包装。

八、总结

  1. TreeSet 是基于 TreeMap(红黑树) 实现的有序、无重复集合,核心是红黑树的自平衡和排序特性;
  2. 所有操作委托 TreeMap 实现,判断重复依赖比较器,而非 equals/hashCode;
  3. 适合需要排序 + 去重的场景,性能低于 HashSet,但具备强大的有序导航能力;
  4. 面试核心考点:底层结构、重复判断、排序规则、与 HashSet/TreeMap 的区别、并发安全。

掌握 TreeSet 的核心,本质是掌握红黑树比较器的原理,这也是理解 Java 有序集合的关键。

相关推荐
迷藏4942 小时前
**RISC-V生态下的嵌入式开发新范式:从指令集到自定义外设的全流程实战**在当前国产化
java·python·risc-v
小松加哲2 小时前
Tomcat 核心原理全解析(含请求流转+组件源码+多应用配置)
java·tomcat·firefox
Lyyaoo.2 小时前
【JAVA基础面经】juc包(java.util.concurrent)
java·开发语言
‎ദ്ദിᵔ.˛.ᵔ₎2 小时前
C++ 继承
开发语言·c++
殇淋狱陌2 小时前
【初始Python】Python学习基础(数据类型、定义、变量、下标、目前的开发语言对比)
开发语言·python·学习
色空大师2 小时前
【nacos下载安装】
java·linux·nacos·ubantu
lsx2024062 小时前
Ruby 迭代器
开发语言
朱一头zcy2 小时前
Java基础复习08:IO流(File类与IO流概述、字节输入输出流、字符输入输出流、缓冲流、字符转换流、对象序列化、打印流、Commons-io包介绍)
java·笔记
一叶飘零_sweeeet2 小时前
击穿 Java 高并发性能瓶颈:伪共享底层原理、缓存行填充与 @Contended 注解全维度深度拆解
java·伪共享