深度剖析 Java TreeSet:从源码探寻有序集合的奥秘

156 阅读46分钟

深度剖析 Java TreeSet:从源码探寻有序集合的奥秘

一、引言

在 Java 的编程世界里,集合框架宛如一座宝藏库,为开发者们提供了各式各样的数据结构来满足不同的需求。其中,TreeSet 作为一种特殊的集合,以其有序性和独特的元素存储方式,在众多场景中发挥着重要作用。对于开发者而言,深入理解 TreeSet 的使用原理,不仅能够更加高效地运用它解决实际问题,还能对 Java 集合框架的设计思想有更深刻的认识。本文将深入到 TreeSet 的源码层面,详细剖析其内部结构、核心方法的实现原理以及性能特点,带领读者全面了解 TreeSet 的奥秘。

二、TreeSet 概述

2.1 基本概念

TreeSet 是 Java 集合框架中的一个类,它实现了 NavigableSet 接口,而 NavigableSet 又继承自 SortedSet 接口。TreeSet 基于红黑树(Red-Black Tree)这种自平衡的二叉搜索树实现,这使得 TreeSet 中的元素会按照自然顺序(元素实现 Comparable 接口)或者指定的比较器(Comparator)顺序进行排序。同时,TreeSet 不允许存储重复的元素,当尝试向 TreeSet 中添加一个已经存在的元素时,添加操作将失败。

2.2 继承关系与接口实现

从类的继承关系和接口实现角度来看,TreeSet 的定义如下:

// 继承自 AbstractSet 类,实现了 NavigableSet、Cloneable 和 Serializable 接口
public class TreeSet<E> extends AbstractSet<E>
    implements NavigableSet<E>, Cloneable, java.io.Serializable
{
    // 类的具体实现将在后续详细分析
}

可以看到,TreeSet 继承自 AbstractSet,这意味着它复用了 AbstractSet 提供的一些基础功能。同时,它实现了 NavigableSet 接口,具备了导航功能,例如可以获取大于、小于、大于等于、小于等于某个元素的元素;实现了 Cloneable 接口,支持对象的克隆操作;实现了 Serializable 接口,支持对象的序列化和反序列化。

2.3 与其他集合的对比

与其他常见的集合实现(如 HashSetLinkedHashSet)相比,TreeSet 具有以下特点:

  • 不允许重复元素:和 HashSetLinkedHashSet 一样,TreeSet 不允许存储重复的元素,当尝试添加重复元素时,添加操作将失败。
  • 有序性TreeSet 会对元素进行排序,元素会按照自然顺序(元素实现 Comparable 接口)或者指定的比较器(Comparator)顺序进行存储。而 HashSet 不保证元素的存储顺序,LinkedHashSet 则按照元素的插入顺序进行存储。
  • 性能特点TreeSet 的插入、删除和查找操作的时间复杂度为 O(logn)O(log n),其中 nn 是集合中元素的数量。这是因为红黑树的高度是对数级别的。而 HashSetLinkedHashSet 的插入、删除和查找操作的平均时间复杂度为 O(1)O(1)

三、TreeSet 的内部结构

3.1 核心属性

TreeSet 类本身并没有定义太多的核心属性,它主要依赖于一个 NavigableMap 实例来存储元素。在 TreeSet 的构造函数中,会创建一个 TreeMap 实例,元素作为 TreeMap 的键存储,所有键对应的值都是一个静态常量对象 PRESENT。以下是相关的源码及注释:

// TreeSet 类的核心属性
private transient NavigableMap<E,Object> m;

// 静态常量对象,作为 TreeMap 中键对应的值
private static final Object PRESENT = new Object();

// 无参构造函数,使用自然顺序创建 TreeSet
public TreeSet() {
    // 创建一个 TreeMap 实例,使用元素的自然顺序进行排序
    this(new TreeMap<E,Object>());
}

// 带比较器的构造函数,使用指定的比较器创建 TreeSet
public TreeSet(Comparator<? super E> comparator) {
    // 创建一个 TreeMap 实例,使用指定的比较器进行排序
    this(new TreeMap<>(comparator));
}

// 带集合参数的构造函数,创建一个包含指定集合中所有元素的 TreeSet
public TreeSet(Collection<? extends E> c) {
    // 创建一个 TreeMap 实例,使用元素的自然顺序进行排序
    this();
    // 将指定集合中的所有元素添加到 TreeSet 中
    addAll(c);
}

// 带 SortedSet 参数的构造函数,创建一个包含指定 SortedSet 中所有元素的 TreeSet
public TreeSet(SortedSet<E> s) {
    // 获取指定 SortedSet 的比较器
    this(s.comparator());
    // 将指定 SortedSet 中的所有元素添加到 TreeSet 中
    addAll(s);
}

// 私有构造函数,用于初始化 TreeSet 的 NavigableMap 实例
TreeSet(NavigableMap<E,Object> m) {
    // 将传入的 NavigableMap 实例赋值给成员变量 m
    this.m = m;
}

从上述源码可以看出,TreeSet 在构造函数中创建了一个 TreeMap 实例,并将其赋值给成员变量 mTreeMap 是基于红黑树实现的,它会根据元素的自然顺序或者指定的比较器顺序对元素进行排序。

3.2 数据存储结构

TreeSet 基于红黑树实现,红黑树是一种自平衡的二叉搜索树,它在每个节点上增加了一个存储位来表示节点的颜色(红色或黑色)。通过对任何一条从根到叶子的路径上各个节点着色方式的限制,红黑树确保没有一条路径会比其他路径长出两倍,因而是接近平衡的。在 TreeSet 中,元素作为 TreeMap 的键存储,所有键对应的值都是一个静态常量对象 PRESENT。红黑树的每个节点包含一个键值对,键就是 TreeSet 中的元素,值是 PRESENT

3.3 初始化过程

TreeSet 的构造函数有多种重载形式,下面分别介绍不同构造函数的源码及注释。

3.3.1 无参构造函数
// 无参构造函数,使用自然顺序创建 TreeSet
public TreeSet() {
    // 创建一个 TreeMap 实例,使用元素的自然顺序进行排序
    this(new TreeMap<E,Object>());
}

在无参构造函数中,调用了私有构造函数,创建了一个 TreeMap 实例,该 TreeMap 使用元素的自然顺序进行排序。

3.3.2 带比较器的构造函数
// 带比较器的构造函数,使用指定的比较器创建 TreeSet
public TreeSet(Comparator<? super E> comparator) {
    // 创建一个 TreeMap 实例,使用指定的比较器进行排序
    this(new TreeMap<>(comparator));
}

在带比较器的构造函数中,调用了私有构造函数,创建了一个 TreeMap 实例,该 TreeMap 使用指定的比较器进行排序。

3.3.3 带集合参数的构造函数
// 带集合参数的构造函数,创建一个包含指定集合中所有元素的 TreeSet
public TreeSet(Collection<? extends E> c) {
    // 创建一个 TreeMap 实例,使用元素的自然顺序进行排序
    this();
    // 将指定集合中的所有元素添加到 TreeSet 中
    addAll(c);
}

在带集合参数的构造函数中,首先调用无参构造函数创建一个使用自然顺序的 TreeMap 实例,然后调用 addAll 方法将指定集合中的所有元素添加到 TreeSet 中。

3.3.4 带 SortedSet 参数的构造函数
// 带 SortedSet 参数的构造函数,创建一个包含指定 SortedSet 中所有元素的 TreeSet
public TreeSet(SortedSet<E> s) {
    // 获取指定 SortedSet 的比较器
    this(s.comparator());
    // 将指定 SortedSet 中的所有元素添加到 TreeSet 中
    addAll(s);
}

在带 SortedSet 参数的构造函数中,首先获取指定 SortedSet 的比较器,调用带比较器的构造函数创建一个使用该比较器的 TreeMap 实例,然后调用 addAll 方法将指定 SortedSet 中的所有元素添加到 TreeSet 中。

四、基本操作的源码分析

4.1 添加操作

4.1.1 add(E e) 方法

add(E e) 方法用于向 TreeSet 中添加一个元素。由于 TreeSet 依赖于 TreeMap 来存储元素,实际上调用的是 TreeMapput 方法。以下是相关的源码及注释:

// TreeSet 中的 add 方法,用于向 TreeSet 中添加一个元素
public boolean add(E e) {
    // 调用 TreeMap 的 put 方法,将元素作为键,PRESENT 作为值插入到 TreeMap 中
    // 如果该键已经存在于 TreeMap 中,put 方法将返回旧值,否则返回 null
    // 如果返回 null,说明元素成功添加到 TreeSet 中,返回 true;否则返回 false
    return m.put(e, PRESENT)==null;
}

// TreeMap 中的 put 方法,用于将键值对插入到 TreeMap 中
public V put(K key, V value) {
    // 获取根节点
    Entry<K,V> t = root;
    // 如果根节点为空
    if (t == null) {
        // 检查键是否为 null,如果为 null 且比较器不允许 null 键,抛出 NullPointerException 异常
        compare(key, key); // type (and possibly null) check

        // 创建一个新的根节点
        root = new Entry<>(key, value, null);
        // 元素数量加 1
        size = 1;
        // 记录结构修改次数
        modCount++;
        return null;
    }
    int cmp;
    Entry<K,V> parent;
    // split comparator and comparable paths
    // 获取比较器
    Comparator<? super K> cpr = comparator;
    // 如果比较器不为 null
    if (cpr != null) {
        // 循环查找插入位置
        do {
            // 记录父节点
            parent = t;
            // 比较键和当前节点的键
            cmp = cpr.compare(key, t.key);
            // 如果键小于当前节点的键
            if (cmp < 0)
                // 向左子树查找
                t = t.left;
            // 如果键大于当前节点的键
            else if (cmp > 0)
                // 向右子树查找
                t = t.right;
            // 如果键等于当前节点的键
            else
                // 更新当前节点的值并返回旧值
                return t.setValue(value);
        } while (t != null);
    }
    // 如果比较器为 null,使用元素的自然顺序进行比较
    else {
        // 如果键为 null,抛出 NullPointerException 异常
        if (key == null)
            throw new NullPointerException();
        @SuppressWarnings("unchecked")
            // 将键转换为 Comparable 类型
            Comparable<? super K> k = (Comparable<? super K>) key;
        // 循环查找插入位置
        do {
            // 记录父节点
            parent = t;
            // 比较键和当前节点的键
            cmp = k.compareTo(t.key);
            // 如果键小于当前节点的键
            if (cmp < 0)
                // 向左子树查找
                t = t.left;
            // 如果键大于当前节点的键
            else if (cmp > 0)
                // 向右子树查找
                t = t.right;
            // 如果键等于当前节点的键
            else
                // 更新当前节点的值并返回旧值
                return t.setValue(value);
        } while (t != null);
    }
    // 创建一个新节点
    Entry<K,V> e = new Entry<>(key, value, parent);
    // 根据比较结果将新节点插入到左子树或右子树
    if (cmp < 0)
        parent.left = e;
    else
        parent.right = e;
    // 插入后进行红黑树的平衡调整
    fixAfterInsertion(e);
    // 元素数量加 1
    size++;
    // 记录结构修改次数
    modCount++;
    return null;
}

add 方法中,首先调用 TreeMapput 方法,将元素作为键,PRESENT 作为值插入到 TreeMap 中。在 put 方法中,会先判断根节点是否为空,如果为空则创建一个新的根节点。然后根据比较器(如果有)或元素的自然顺序查找插入位置,若找到相同键的节点,则更新其值并返回旧值;若未找到,则创建一个新节点并插入到合适的位置。插入后会调用 fixAfterInsertion 方法进行红黑树的平衡调整,以保证红黑树的性质。

4.1.2 addAll(Collection<? extends E> c) 方法

addAll(Collection<? extends E> c) 方法用于将指定集合中的所有元素添加到 TreeSet 中。它调用的是 AbstractCollectionaddAll 方法,而 AbstractCollectionaddAll 方法会遍历指定集合中的每个元素,调用 add 方法将元素添加到 TreeSet 中。以下是相关的源码及注释:

// TreeSet 中的 addAll 方法,用于将指定集合中的所有元素添加到 TreeSet 中
public boolean addAll(Collection<? extends E> c) {
    // Use linear-time version if applicable
    // 如果指定集合是 SortedSet 类型且当前 TreeSet 的比较器和指定 SortedSet 的比较器相同
    if (m.size()==0 && c.size() > 0 &&
        c instanceof SortedSet &&
        m instanceof TreeMap) {
        // 获取指定 SortedSet
        SortedSet<? extends E> set = (SortedSet<? extends E>) c;
        // 获取当前 TreeSet 的 TreeMap 实例
        TreeMap<E,Object> map = (TreeMap<E,Object>) m;
        // 获取指定 SortedSet 的比较器
        Comparator<? super E> cc = (Comparator<? super E>) set.comparator();
        // 获取当前 TreeSet 的比较器
        Comparator<? super E> mc = map.comparator();
        // 如果两个比较器相同
        if (cc==mc || (cc != null && cc.equals(mc))) {
            // 调用 TreeMap 的 addAllForTreeSet 方法进行批量添加
            map.addAllForTreeSet(set, PRESENT);
            return true;
        }
    }
    // 调用父类 AbstractCollection 的 addAll 方法
    return super.addAll(c);
}

// AbstractCollection 中的 addAll 方法
public boolean addAll(Collection<? extends E> c) {
    // 标记是否有元素被添加到集合中
    boolean modified = false;
    // 遍历指定集合中的每个元素
    for (E e : c)
        // 调用 add 方法将元素添加到集合中
        // 如果元素成功添加,将 modified 标记为 true
        if (add(e))
            modified = true;
    return modified;
}

addAll 方法中,首先判断是否满足使用线性时间版本的条件,如果满足则调用 TreeMapaddAllForTreeSet 方法进行批量添加;否则调用父类 AbstractCollectionaddAll 方法,该方法会遍历指定集合中的每个元素,调用 add 方法将元素添加到 TreeSet 中。

4.2 删除操作

4.2.1 remove(Object o) 方法

remove(Object o) 方法用于从 TreeSet 中移除指定的元素。它调用的是 TreeMapremove 方法。以下是相关的源码及注释:

// TreeSet 中的 remove 方法,用于从 TreeSet 中移除指定的元素
public boolean remove(Object o) {
    // 调用 TreeMap 的 remove 方法,移除指定键对应的键值对
    // 如果该键存在于 TreeMap 中,remove 方法将返回旧值,否则返回 null
    // 如果返回值不为 null,说明元素成功从 TreeSet 中移除,返回 true;否则返回 false
    return m.remove(o)==PRESENT;
}

// TreeMap 中的 remove 方法,用于移除指定键对应的键值对
public V remove(Object key) {
    // 查找指定键对应的节点
    Entry<K,V> p = getEntry(key);
    // 如果节点为空,返回 null
    if (p == null)
        return null;

    // 获取旧值
    V oldValue = p.value;
    // 移除节点
    deleteEntry(p);
    // 返回旧值
    return oldValue;
}

// TreeMap 中的 getEntry 方法,用于查找指定键对应的节点
final Entry<K,V> getEntry(Object key) {
    // 如果比较器不为 null,调用 getEntryUsingComparator 方法查找节点
    if (comparator != null)
        return getEntryUsingComparator(key);
    // 如果键为 null,抛出 NullPointerException 异常
    if (key == null)
        throw new NullPointerException();
    @SuppressWarnings("unchecked")
        // 将键转换为 Comparable 类型
        Comparable<? super K> k = (Comparable<? super K>) key;
    // 获取根节点
    Entry<K,V> p = root;
    // 循环查找节点
    while (p != null) {
        // 比较键和当前节点的键
        int cmp = k.compareTo(p.key);
        // 如果键小于当前节点的键
        if (cmp < 0)
            // 向左子树查找
            p = p.left;
        // 如果键大于当前节点的键
        else if (cmp > 0)
            // 向右子树查找
            p = p.right;
        // 如果键等于当前节点的键
        else
            // 返回当前节点
            return p;
    }
    // 未找到节点,返回 null
    return null;
}

// TreeMap 中的 getEntryUsingComparator 方法,用于使用比较器查找指定键对应的节点
final Entry<K,V> getEntryUsingComparator(Object key) {
    @SuppressWarnings("unchecked")
        // 将键转换为 K 类型
        K k = (K) key;
    // 获取比较器
    Comparator<? super K> cpr = comparator;
    // 如果比较器不为 null
    if (cpr != null) {
        // 获取根节点
        Entry<K,V> p = root;
        // 循环查找节点
        while (p != null) {
            // 比较键和当前节点的键
            int cmp = cpr.compare(k, p.key);
            // 如果键小于当前节点的键
            if (cmp < 0)
                // 向左子树查找
                p = p.left;
            // 如果键大于当前节点的键
            else if (cmp > 0)
                // 向右子树查找
                p = p.right;
            // 如果键等于当前节点的键
            else
                // 返回当前节点
                return p;
        }
    }
    // 未找到节点,返回 null
    return null;
}

// TreeMap 中的 deleteEntry 方法,用于移除指定节点
private void deleteEntry(Entry<K,V> p) {
    // 元素数量减 1
    size--;

    // If strictly internal, copy successor's element to p and then make p
    // point to successor.
    // 如果节点有左右子节点
    if (p.left != null && p.right != null) {
        // 查找后继节点
        Entry<K,V> s = successor(p);
        // 将后继节点的键和值复制到当前节点
        p.key = s.key;
        p.value = s.value;
        // 将 p 指向后继节点
        p = s;
    } // p has 2 children

    // Start fixup at replacement node, if it exists.
    // 获取替换节点
    Entry<K,V> replacement = (p.left != null ? p.left : p.right);

    // 如果替换节点不为 null
    if (replacement != null) {
        // Link replacement to parent
        // 设置替换节点的父节点为当前节点的父节点
        replacement.parent = p.parent;
        // 如果当前节点是根节点
        if (p.parent == null)
            // 将替换节点设置为根节点
            root = replacement;
        // 如果当前节点是其父节点的左子节点
        else if (p == p.parent.left)
            // 将替换节点设置为其父节点的左子节点
            p.parent.left  = replacement;
        // 如果当前节点是其父节点的右子节点
        else
            // 将替换节点设置为其父节点的右子节点
            p.parent.right = replacement;

        // Null out links so they are OK to use by fixAfterDeletion.
        // 将当前节点的左右子节点和父节点置为 null
        p.left = p.right = p.parent = null;

        // Fix replacement
        // 如果当前节点是黑色节点,进行红黑树的平衡调整
        if (p.color == BLACK)
            fixAfterDeletion(replacement);
    } else if (p.parent == null) { // return if we are the only node.
        // 如果当前节点是根节点且没有子节点,将根节点置为 null
        root = null;
    } else { //  No children. Use self as phantom replacement and unlink.
        // 如果当前节点是黑色节点,进行红黑树的平衡调整
        if (p.color == BLACK)
            fixAfterDeletion(p);

        // 从父节点中移除当前节点
        if (p.parent != null) {
            if (p == p.parent.left)
                p.parent.left = null;
            else if (p == p.parent.right)
                p.parent.right = null;
            p.parent = null;
        }
    }
}

remove 方法中,首先调用 TreeMapgetEntry 方法查找指定键对应的节点,如果节点存在,则调用 deleteEntry 方法移除该节点。在 deleteEntry 方法中,会根据节点的子节点情况进行不同的处理。如果节点有左右子节点,会找到其后继节点并将后继节点的键和值复制到当前节点,然后将 p 指向后继节点。接着找到替换节点,将替换节点连接到父节点,并根据节点的颜色进行红黑树的平衡调整。

4.2.2 removeAll(Collection<?> c) 方法

removeAll(Collection<?> c) 方法用于从 TreeSet 中移除指定集合中包含的所有元素。它调用的是 AbstractSetremoveAll 方法。以下是相关的源码及注释:

// TreeSet 中的 removeAll 方法,用于从 TreeSet 中移除指定集合中包含的所有元素
public boolean removeAll(Collection<?> c) {
    // 检查输入的集合是否为 null,如果为 null 则抛出 NullPointerException 异常
    Objects.requireNonNull(c);
    // 标记是否有元素被从 TreeSet 中移除
    boolean modified = false;

    // 如果指定集合的大小大于当前 TreeSet 的大小
    if (size() > c.size()) {
        // 遍历指定集合中的每个元素
        for (Iterator<?> i = c.iterator(); i.hasNext(); )
            // 调用 remove 方法移除该元素
            // 如果元素成功移除,将 modified 标记为 true
            if (remove(i.next()))
                modified = true;
    } else {
        // 如果指定集合的大小小于等于当前 TreeSet 的大小
        // 获取当前 TreeSet 的迭代器
        for (Iterator<?> i = iterator(); i.hasNext(); ) {
            // 如果指定集合包含当前元素
            if (c.contains(i.next())) {
                // 调用迭代器的 remove 方法移除该元素
                i.remove();
                // 将 modified 标记为 true
                modified = true;
            }
        }
    }
    return modified;
}

// AbstractSet 中的 removeAll 方法
public boolean removeAll(Collection<?> c) {
    // 检查输入的集合是否为 null,如果为 null 则抛出 NullPointerException 异常
    Objects.requireNonNull(c);
    // 标记是否有元素被从集合中移除
    boolean modified = false;

    // 如果指定集合的大小大于当前集合的大小
    if (size() > c.size()) {
        // 遍历指定集合中的每个元素
        for (Iterator<?> i = c.iterator(); i.hasNext(); )
            // 调用 remove 方法移除该元素
            // 如果元素成功移除,将 modified 标记为 true
            if (remove(i.next()))
                modified = true;
    } else {
        // 如果指定集合的大小小于等于当前集合的大小
        // 获取当前集合的迭代器
        for (Iterator<?> i = iterator(); i.hasNext(); ) {
            // 如果指定集合包含当前元素
            if (c.contains(i.next())) {
                // 调用迭代器的 remove 方法移除该元素
                i.remove();
                // 将 modified 标记为 true
                modified = true;
            }
        }
    }
    return modified;
}

removeAll 方法中,首先检查输入的集合是否为 null,如果为 null 则抛出 NullPointerException 异常。然后根据当前 TreeSet 的大小和指定集合的大小选择不同的遍历方式。如果当前 TreeSet 的大小大于指定集合的大小,遍历指定集合中的每个元素,调用 remove 方法移除该元素;如果当前 TreeSet 的大小小于等于指定集合的大小,遍历当前 TreeSet 中的每个元素,如果指定集合包含该元素,调用迭代器的 remove 方法移除该元素。最后返回 modified 标记。

4.2.3 clear() 方法

clear() 方法用于清空 TreeSet 中的所有元素。它调用的是 TreeMapclear 方法。以下是相关的源码及注释:

// TreeSet 中的 clear 方法,用于清空 TreeSet 中的所有元素
public void clear() {
    // 调用 TreeMap 的 clear 方法,清空 TreeMap 中的所有键值对
    m.clear();
}

// TreeMap 中的 clear 方法
public void clear() {
    // 记录结构修改次数
    modCount++;
    // 元素数量置为 0
    size = 0;
    // 根节点置为 null
    root = null;
}

clear 方法中,调用 TreeMapclear 方法,将 TreeMap 的根节点置为 null,元素数量置为 0,并记录结构修改次数。

4.3 查找操作

4.3.1 contains(Object o) 方法

contains(Object o) 方法用于检查 TreeSet 中是否包含指定的元素。它调用的是 TreeMapcontainsKey 方法。以下是相关的源码及注释:

// TreeSet 中的 contains 方法,用于检查 TreeSet 中是否包含指定的元素
public boolean contains(Object o) {
    // 调用 TreeMap 的 containsKey 方法,检查 TreeMap 中是否包含指定的键
    // 如果包含,说明 TreeSet 中包含该元素,返回 true;否则返回 false
    return m.containsKey(o);
}

// TreeMap 中的 containsKey 方法,用于检查 TreeMap 中是否包含指定的键
public boolean containsKey(Object key) {
    // 调用 getEntry 方法查找指定键对应的节点
    return getEntry(key) != null;
}

// TreeMap 中的 getEntry 方法,用于查找指定键对应的节点
final Entry<K,V> getEntry(Object key) {
    // 如果比较器不为 null,调用 getEntryUsingComparator 方法查找节点
    if (comparator != null)
        return getEntryUsingComparator(key);
    // 如果键为 null,抛出 NullPointerException 异常
    if (key == null)
        throw new NullPointerException();
    @SuppressWarnings("unchecked")
        // 将键转换为 Comparable 类型
        Comparable<? super K> k = (Comparable<? super K>) key;
    // 获取根节点
    Entry<K,V> p = root;
    // 循环查找节点
    while (p != null) {
        // 比较键和当前节点的键
        int cmp = k.compareTo(p.key);
        // 如果键小于当前节点的键
        if (cmp < 0)
            // 向左子树查找
            p = p.left;
        // 如果键大于当前节点的键
        else if (cmp > 0)
            // 向右子树查找
            p = p.right;
        // 如果键等于当前节点的键
        else
            // 返回当前节点
            return p;
    }
    // 未找到节点,返回 null
    return null;
}

// TreeMap 中的 getEntryUsingComparator 方法,用于使用比较器查找指定键对应的节点
final Entry<K,V> getEntryUsingComparator(Object key) {
    @SuppressWarnings("unchecked")
        // 将键转换为 K 类型
        K k = (K) key;
    // 获取比较器
    Comparator<? super K> cpr = comparator;
    // 如果比较器不为 null
    if (cpr != null) {
        // 获取根节点
        Entry<K,V> p = root;
        // 循环查找节点
        while (p != null) {
            // 比较键和当前节点的键
            int cmp = cpr.compare(k, p.key);
            // 如果键小于当前节点的键
            if (cmp < 0)
                // 向左子树查找
                p = p.left;
            // 如果键大于当前节点的键
            else if (cmp > 0)
                // 向右子树查找
                p = p.right;
            // 如果键等于当前节点的键
            else
                // 返回当前节点
                return p;
        }
    }
    // 未找到节点,返回 null
    return null;
}

contains 方法中,调用 TreeMapcontainsKey 方法,该方法又调用 getEntry 方法查找指定键对应的节点。在 getEntry 方法中,会根据比较器(如果有)或元素的自然顺序查找节点,如果找到节点则返回该节点,否则返回 null。如果返回值不为 null,说明 TreeSet 中包含该元素,返回 true;否则返回 false

4.4 其他操作

4.4.1 size() 方法

size() 方法用于返回 TreeSet 中元素的数量。它调用的是 TreeMapsize 方法。以下是相关的源码及注释:

// TreeSet 中的 size 方法,用于返回 TreeSet 中元素的数量
public int size() {
    // 调用 TreeMap 的 size 方法,返回 TreeMap 中键值对的数量,即 TreeSet 中元素的数量
    return m.size();
}

// TreeMap 中的 size 方法
public int size() {
    // 返回元素数量
    return size;
}

size 方法中,调用 TreeMapsize 方法,返回 TreeMap 中键值对的数量,也就是 TreeSet 中元素的数量。

4.4.2 isEmpty() 方法

isEmpty() 方法用于检查 TreeSet 是否为空。它调用的是 TreeMapisEmpty 方法。以下是相关的源码及注释:

// TreeSet 中的 isEmpty 方法,用于检查 TreeSet 是否为空
public boolean isEmpty() {
    // 调用 TreeMap 的 isEmpty 方法,检查 TreeMap 中键值对的数量是否为 0
    // 如果为 0,说明 TreeSet 为空,返回 true;否则返回 false
    return m.isEmpty();
}

// TreeMap 中的 isEmpty 方法
public boolean isEmpty() {
    // 返回元素数量是否为 0
    return size == 0;
}

isEmpty 方法中,调用 TreeMapisEmpty 方法,检查 TreeMap 中键值对的数量是否为 0,如果为 0 则返回 true,表示 TreeSet 为空;否则返回 false

4.4.3 iterator() 方法

iterator() 方法用于返回一个迭代器,用于遍历 TreeSet 中的元素。由于 TreeSet 是有序的,迭代器会按照元素的排序顺序依次遍历元素。它调用的是 TreeMapnavigableKeySet().iterator() 方法。以下是相关的源码及注释:

// TreeSet 中的 iterator 方法,用于返回一个迭代器,用于遍历 TreeSet 中的元素
public Iterator<E> iterator() {
    // 调用 TreeMap 的 navigableKeySet 方法返回可导航的键集合,然后调用键集合的 iterator 方法返回迭代器
    return m.navigableKeySet().iterator();
}

// TreeMap 中的 navigableKeySet 方法,返回可导航的键集合
public NavigableSet<K> navigableKeySet() {
    // 获取键集合对象,如果键集合对象为 null,则创建一个新的键集合对象
    KeySet<K> nks = navigableKeySet;
    return (nks != null) ? nks : (navigableKeySet = new KeySet<>(this));
}

// TreeMap 中 KeySet 类的定义
static final class KeySet<E> extends AbstractSet<E> implements NavigableSet<E> {
    // 关联的 TreeMap 实例
    private final NavigableMap<E, ?> m;
    // 构造函数,初始化关联的 TreeMap 实例
    KeySet(NavigableMap<E,?> map) { m = map; }

    // 返回键集合中元素的数量
    public final int size()                 { return m.size(); }
    // 清空键集合中的所有元素
    public final void clear()               { m.clear(); }
    // 返回一个迭代器,用于遍历键集合中的元素
    public final Iterator<E> iterator() {
        if (m instanceof TreeMap)
            return ((TreeMap<E,?>)m).keyIterator();
        else
            return m.navigableKeySet().iterator();
    }
    // 检查键集合中是否包含指定的元素
    // 检查键集合中是否包含指定的元素
    public final boolean contains(Object o) { return m.containsKey(o); }
    // 添加元素到键集合中,由于键集合是只读的,该方法会抛出 UnsupportedOperationException 异常
    public final boolean add(E e) {
        throw new UnsupportedOperationException();
    }
    // 从键集合中移除指定的元素
    public final boolean remove(Object o) {
        return m.remove(o) != null;
    }
    // 返回键集合中第一个元素
    public final E first() { return m.firstKey(); }
    // 返回键集合中最后一个元素
    public final E last() { return m.lastKey(); }
    // 返回键集合中小于指定元素的最大元素
    public final E lower(E e) { return m.lowerKey(e); }
    // 返回键集合中小于等于指定元素的最大元素
    public final E floor(E e) { return m.floorKey(e); }
    // 返回键集合中大于等于指定元素的最小元素
    public final E ceiling(E e) { return m.ceilingKey(e); }
    // 返回键集合中大于指定元素的最小元素
    public final E higher(E e) { return m.higherKey(e); }
    // 返回键集合的逆序视图
    public final NavigableSet<E> descendingSet() {
        return new KeySet<>(m.descendingMap());
    }
    // 返回一个逆序迭代器,用于遍历键集合中的元素
    public final Iterator<E> descendingIterator() {
        return descendingSet().iterator();
    }
    // 返回键集合中从 fromElement 到 toElement 的子集合
    public final NavigableSet<E> subSet(E fromElement, boolean fromInclusive,
                                        E toElement,   boolean toInclusive) {
        return new KeySet<>(m.subMap(fromElement, fromInclusive,
                                     toElement,   toInclusive));
    }
    // 返回键集合中小于 toElement 的子集合
    public final NavigableSet<E> headSet(E toElement, boolean inclusive) {
        return new KeySet<>(m.headMap(toElement, inclusive));
    }
    // 返回键集合中大于 fromElement 的子集合
    public final NavigableSet<E> tailSet(E fromElement, boolean inclusive) {
        return new KeySet<>(m.tailMap(fromElement, inclusive));
    }
    // 返回键集合中从 fromElement 到 toElement 的子集合
    public final SortedSet<E> subSet(E fromElement, E toElement) {
        return subSet(fromElement, true, toElement, false);
    }
    // 返回键集合中小于 toElement 的子集合
    public final SortedSet<E> headSet(E toElement) {
        return headSet(toElement, false);
    }
    // 返回键集合中大于 fromElement 的子集合
    public final SortedSet<E> tailSet(E fromElement) {
        return tailSet(fromElement, true);
    }
    // 返回键集合的比较器
    public final Comparator<? super E> comparator() { return m.comparator(); }
}

iterator() 方法中,TreeSet 调用 TreeMapnavigableKeySet().iterator() 来获取迭代器。TreeMapnavigableKeySet() 方法返回一个 KeySet 对象,这个 KeySet 对象实现了 NavigableSet 接口,提供了一系列与导航相关的方法。KeySetiterator() 方法最终会返回一个能够按照元素排序顺序遍历键集合的迭代器。

4.4.4 subSet(E fromElement, boolean fromInclusive, E toElement, boolean toInclusive) 方法

subSet(E fromElement, boolean fromInclusive, E toElement, boolean toInclusive) 方法用于返回 TreeSet 中从 fromElementtoElement 的子集合。这个子集合是一个视图,对它的修改会反映到原 TreeSet 中,反之亦然。以下是相关源码及注释:

// TreeSet 中的 subSet 方法,返回从 fromElement 到 toElement 的子集合
public NavigableSet<E> subSet(E fromElement, boolean fromInclusive,
                              E toElement,   boolean toInclusive) {
    // 调用 TreeMap 的 subMap 方法获取子映射,然后使用这个子映射创建一个新的 TreeSet
    return new TreeSet<>(m.subMap(fromElement, fromInclusive,
                                  toElement,   toInclusive));
}

// TreeMap 中的 subMap 方法,返回从 fromKey 到 toKey 的子映射
public NavigableMap<K,V> subMap(K fromKey, boolean fromInclusive,
                                K toKey,   boolean toInclusive) {
    // 检查 fromKey 和 toKey 是否满足条件
    if (fromKey == null || toKey == null)
        throw new NullPointerException();
    // 比较 fromKey 和 toKey
    if (compare(fromKey, toKey) > 0)
        throw new IllegalArgumentException("fromKey > toKey");
    // 创建一个 SubMap 对象
    return new SubMap(this,
                      false, fromKey, fromInclusive,
                      false, toKey,   toInclusive);
}

// TreeMap 中 SubMap 类的定义
static final class SubMap<K,V> extends AscendingSubMap<K,V> {
    // 构造函数,初始化 SubMap 对象
    SubMap(TreeMap<K,V> m,
           boolean reverseOrder,
           K fromStart, boolean startInclusive,
           K toEnd,   boolean endInclusive) {
        super(m, reverseOrder,
              fromStart, startInclusive,
              toEnd,   endInclusive);
    }
}

// TreeMap 中 AscendingSubMap 类的定义
abstract static class AscendingSubMap<K,V> extends AbstractMap<K,V>
    implements NavigableMap<K,V>, java.io.Serializable {
    // 关联的 TreeMap 实例
    final TreeMap<K,V> m;
    // 起始键
    final K fromStart;
    // 起始键是否包含在子映射中
    final boolean startInclusive;
    // 结束键
    final K toEnd;
    // 结束键是否包含在子映射中
    final boolean endInclusive;
    // 构造函数,初始化 AscendingSubMap 对象
    AscendingSubMap(TreeMap<K,V> m,
                    boolean reverseOrder,
                    K fromStart, boolean startInclusive,
                    K toEnd,   boolean endInclusive) {
        this.m = m;
        this.fromStart = fromStart;
        this.startInclusive = startInclusive;
        this.toEnd = toEnd;
        this.endInclusive = endInclusive;
    }
    // 其他方法实现...
}

subSet 方法中,TreeSet 调用 TreeMapsubMap 方法获取一个子映射,然后使用这个子映射创建一个新的 TreeSetTreeMapsubMap 方法会创建一个 SubMap 对象,这个 SubMap 对象继承自 AscendingSubMap,它维护了子映射的起始键、结束键以及是否包含这些键的信息。

4.4.5 headSet(E toElement, boolean inclusive) 方法

headSet(E toElement, boolean inclusive) 方法用于返回 TreeSet 中小于(或小于等于,取决于 inclusive 参数)toElement 的子集合。以下是相关源码及注释:

// TreeSet 中的 headSet 方法,返回小于 toElement 的子集合
public NavigableSet<E> headSet(E toElement, boolean inclusive) {
    // 调用 TreeMap 的 headMap 方法获取子映射,然后使用这个子映射创建一个新的 TreeSet
    return new TreeSet<>(m.headMap(toElement, inclusive));
}

// TreeMap 中的 headMap 方法,返回小于 toKey 的子映射
public NavigableMap<K,V> headMap(K toKey, boolean inclusive) {
    // 检查 toKey 是否为 null
    if (toKey == null)
        throw new NullPointerException();
    // 创建一个 SubMap 对象
    return new SubMap(this,
                      false, m.getFirstKey(), true,
                      false, toKey,   inclusive);
}

headSet 方法中,TreeSet 调用 TreeMapheadMap 方法获取一个子映射,然后使用这个子映射创建一个新的 TreeSetTreeMapheadMap 方法同样会创建一个 SubMap 对象,该对象的起始键为 TreeMap 的第一个键,结束键为 toKey

4.4.6 tailSet(E fromElement, boolean inclusive) 方法

tailSet(E fromElement, boolean inclusive) 方法用于返回 TreeSet 中大于(或大于等于,取决于 inclusive 参数)fromElement 的子集合。以下是相关源码及注释:

// TreeSet 中的 tailSet 方法,返回大于 fromElement 的子集合
public NavigableSet<E> tailSet(E fromElement, boolean inclusive) {
    // 调用 TreeMap 的 tailMap 方法获取子映射,然后使用这个子映射创建一个新的 TreeSet
    return new TreeSet<>(m.tailMap(fromElement, inclusive));
}

// TreeMap 中的 tailMap 方法,返回大于 fromKey 的子映射
public NavigableMap<K,V> tailMap(K fromKey, boolean inclusive) {
    // 检查 fromKey 是否为 null
    if (fromKey == null)
        throw new NullPointerException();
    // 创建一个 SubMap 对象
    return new SubMap(this,
                      false, fromKey, inclusive,
                      false, m.getLastKey(), true);
}

tailSet 方法中,TreeSet 调用 TreeMaptailMap 方法获取一个子映射,然后使用这个子映射创建一个新的 TreeSetTreeMaptailMap 方法创建的 SubMap 对象,其起始键为 fromKey,结束键为 TreeMap 的最后一个键。

4.4.7 first() 方法

first() 方法用于返回 TreeSet 中的第一个元素,也就是最小的元素。以下是相关源码及注释:

// TreeSet 中的 first 方法,返回 TreeSet 中的第一个元素
public E first() {
    // 调用 TreeMap 的 firstKey 方法返回第一个键,即 TreeSet 中的第一个元素
    return m.firstKey();
}

// TreeMap 中的 firstKey 方法
public K firstKey() {
    // 获取根节点
    Entry<K,V> p = root;
    // 如果根节点为空,抛出 NoSuchElementException 异常
    if (p == null)
        throw new NoSuchElementException();
    // 向左子树遍历,直到找到最左边的节点
    while (p.left != null)
        p = p.left;
    // 返回最左边节点的键
    return p.key;
}

first 方法中,TreeSet 调用 TreeMapfirstKey 方法。TreeMapfirstKey 方法会从根节点开始,不断向左子树遍历,直到找到最左边的节点,该节点的键就是 TreeMap 中的第一个键,也就是 TreeSet 中的第一个元素。

4.4.8 last() 方法

last() 方法用于返回 TreeSet 中的最后一个元素,也就是最大的元素。以下是相关源码及注释:

// TreeSet 中的 last 方法,返回 TreeSet 中的最后一个元素
public E last() {
    // 调用 TreeMap 的 lastKey 方法返回最后一个键,即 TreeSet 中的最后一个元素
    return m.lastKey();
}

// TreeMap 中的 lastKey 方法
public K lastKey() {
    // 获取根节点
    Entry<K,V> p = root;
    // 如果根节点为空,抛出 NoSuchElementException 异常
    if (p == null)
        throw new NoSuchElementException();
    // 向右子树遍历,直到找到最右边的节点
    while (p.right != null)
        p = p.right;
    // 返回最右边节点的键
    return p.key;
}

last 方法中,TreeSet 调用 TreeMaplastKey 方法。TreeMaplastKey 方法会从根节点开始,不断向右子树遍历,直到找到最右边的节点,该节点的键就是 TreeMap 中的最后一个键,也就是 TreeSet 中的最后一个元素。

4.4.9 lower(E e) 方法

lower(E e) 方法用于返回 TreeSet 中小于指定元素 e 的最大元素。以下是相关源码及注释:

// TreeSet 中的 lower 方法,返回小于 e 的最大元素
public E lower(E e) {
    // 调用 TreeMap 的 lowerKey 方法返回小于 e 的最大键,即 TreeSet 中小于 e 的最大元素
    return m.lowerKey(e);
}

// TreeMap 中的 lowerKey 方法
public K lowerKey(K key) {
    // 调用 getLowerEntry 方法查找小于 key 的最大节点
    Entry<K,V> p = getLowerEntry(key);
    // 如果节点为空,返回 null;否则返回节点的键
    return (p == null)? null : p.key;
}

// TreeMap 中的 getLowerEntry 方法
final Entry<K,V> getLowerEntry(K key) {
    // 获取根节点
    Entry<K,V> p = root;
    // 当根节点不为空时进行查找
    while (p != null) {
        // 比较 key 和当前节点的键
        int cmp = compare(key, p.key);
        // 如果 key 大于当前节点的键
        if (cmp > 0) {
            // 如果当前节点的右子节点不为空
            if (p.right != null)
                // 向右子树查找
                p = p.right;
            else
                // 否则返回当前节点
                return p;
        } else {
            // 如果 key 小于等于当前节点的键
            if (p.left != null) {
                // 向左子树查找
                p = p.left;
            } else {
                // 回溯到父节点
                Entry<K,V> parent = p.parent;
                Entry<K,V> ch = p;
                // 当父节点不为空且当前节点是父节点的左子节点时
                while (parent != null && ch == parent.left) {
                    // 继续回溯
                    ch = parent;
                    parent = parent.parent;
                }
                // 返回父节点
                return parent;
            }
        }
    }
    // 未找到符合条件的节点,返回 null
    return null;
}

lower 方法中,TreeSet 调用 TreeMaplowerKey 方法。TreeMaplowerKey 方法会调用 getLowerEntry 方法查找小于指定键的最大节点。getLowerEntry 方法会从根节点开始,根据比较结果在树中进行查找,如果找到合适的节点则返回该节点,否则返回 null

4.4.10 floor(E e) 方法

floor(E e) 方法用于返回 TreeSet 中小于等于指定元素 e 的最大元素。以下是相关源码及注释:

// TreeSet 中的 floor 方法,返回小于等于 e 的最大元素
public E floor(E e) {
    // 调用 TreeMap 的 floorKey 方法返回小于等于 e 的最大键,即 TreeSet 中小于等于 e 的最大元素
    return m.floorKey(e);
}

// TreeMap 中的 floorKey 方法
public K floorKey(K key) {
    // 调用 getFloorEntry 方法查找小于等于 key 的最大节点
    Entry<K,V> p = getFloorEntry(key);
    // 如果节点为空,返回 null;否则返回节点的键
    return (p == null)? null : p.key;
}

// TreeMap 中的 getFloorEntry 方法
final Entry<K,V> getFloorEntry(K key) {
    // 获取根节点
    Entry<K,V> p = root;
    // 当根节点不为空时进行查找
    while (p != null) {
        // 比较 key 和当前节点的键
        int cmp = compare(key, p.key);
        // 如果 key 大于当前节点的键
        if (cmp > 0) {
            // 如果当前节点的右子节点不为空
            if (p.right != null)
                // 向右子树查找
                p = p.right;
            else
                // 否则返回当前节点
                return p;
        } else if (cmp < 0) {
            // 如果 key 小于当前节点的键
            if (p.left != null) {
                // 向左子树查找
                p = p.left;
            } else {
                // 回溯到父节点
                Entry<K,V> parent = p.parent;
                Entry<K,V> ch = p;
                // 当父节点不为空且当前节点是父节点的左子节点时
                while (parent != null && ch == parent.left) {
                    // 继续回溯
                    ch = parent;
                    parent = parent.parent;
                }
                // 返回父节点
                return parent;
            }
        } else
            // 如果 key 等于当前节点的键,返回当前节点
            return p;

    }
    // 未找到符合条件的节点,返回 null
    return null;
}

floor 方法中,TreeSet 调用 TreeMapfloorKey 方法。TreeMapfloorKey 方法会调用 getFloorEntry 方法查找小于等于指定键的最大节点。getFloorEntry 方法的查找逻辑与 getLowerEntry 方法类似,但会处理键相等的情况。

4.4.11 ceiling(E e) 方法

ceiling(E e) 方法用于返回 TreeSet 中大于等于指定元素 e 的最小元素。以下是相关源码及注释:

// TreeSet 中的 ceiling 方法,返回大于等于 e 的最小元素
public E ceiling(E e) {
    // 调用 TreeMap 的 ceilingKey 方法返回大于等于 e 的最小键,即 TreeSet 中大于等于 e 的最小元素
    return m.ceilingKey(e);
}

// TreeMap 中的 ceilingKey 方法
public K ceilingKey(K key) {
    // 调用 getCeilingEntry 方法查找大于等于 key 的最小节点
    Entry<K,V> p = getCeilingEntry(key);
    // 如果节点为空,返回 null;否则返回节点的键
    return (p == null)? null : p.key;
}

// TreeMap 中的 getCeilingEntry 方法
final Entry<K,V> getCeilingEntry(K key) {
    // 获取根节点
    Entry<K,V> p = root;
    // 当根节点不为空时进行查找
    while (p != null) {
        // 比较 key 和当前节点的键
        int cmp = compare(key, p.key);
        // 如果 key 小于当前节点的键
        if (cmp < 0) {
            // 如果当前节点的左子节点不为空
            if (p.left != null)
                // 向左子树查找
                p = p.left;
            else
                // 否则返回当前节点
                return p;
        } else if (cmp > 0) {
            // 如果 key 大于当前节点的键
            if (p.right != null) {
                // 向右子树查找
                p = p.right;
            } else {
                // 回溯到父节点
                Entry<K,V> parent = p.parent;
                Entry<K,V> ch = p;
                // 当父节点不为空且当前节点是父节点的右子节点时
                while (parent != null && ch == parent.right) {
                    // 继续回溯
                    ch = parent;
                    parent = parent.parent;
                }
                // 返回父节点
                return parent;
            }
        } else
            // 如果 key 等于当前节点的键,返回当前节点
            return p;
    }
    // 未找到符合条件的节点,返回 null
    return null;
}

ceiling 方法中,TreeSet 调用 TreeMapceilingKey 方法。TreeMapceilingKey 方法会调用 getCeilingEntry 方法查找大于等于指定键的最小节点。getCeilingEntry 方法的查找逻辑与 getFloorEntry 方法类似,但查找方向相反。

4.4.12 higher(E e) 方法

higher(E e) 方法用于返回 TreeSet 中大于指定元素 e 的最小元素。以下是相关源码及注释:

// TreeSet 中的 higher 方法,返回大于 e 的最小元素
public E higher(E e) {
    // 调用 TreeMap 的 higherKey 方法返回大于 e 的最小键,即 TreeSet 中大于 e 的最小元素
    return m.higherKey(e);
}

// TreeMap 中的 higherKey 方法
public K higherKey(K key) {
    // 调用 getHigherEntry 方法查找大于 key 的最小节点
    Entry<K,V> p = getHigherEntry(key);
    // 如果节点为空,返回 null;否则返回节点的键
    return (p == null)? null : p.key;
}

// TreeMap 中的 getHigherEntry 方法
final Entry<K,V> getHigherEntry(K key) {
    // 获取根节点
    Entry<K,V> p = root;
    // 当根节点不为空时进行查找
    while (p != null) {
        // 比较 key 和当前节点的键
        int cmp = compare(key, p.key);
        // 如果 key 小于当前节点的键
        if (cmp < 0) {
            // 如果当前节点的左子节点不为空
            if (p.left != null)
                // 向左子树查找
                p = p.left;
            else
                // 否则返回当前节点
                return p;
        } else {
            // 如果 key 大于等于当前节点的键
            if (p.right != null) {
                // 向右子树查找
                p = p.right;
            } else {
                // 回溯到父节点
                Entry<K,V> parent = p.parent;
                Entry<K,V> ch = p;
                // 当父节点不为空且当前节点是父节点的右子节点时
                while (parent != null && ch == parent.right) {
                    // 继续回溯
                    ch = parent;
                    parent = parent.parent;
                }
                // 返回父节点
                return parent;
            }
        }
    }
    // 未找到符合条件的节点,返回 null
    return null;
}

higher 方法中,TreeSet 调用 TreeMaphigherKey 方法。TreeMaphigherKey 方法会调用 getHigherEntry 方法查找大于指定键的最小节点。getHigherEntry 方法的查找逻辑与 getLowerEntry 方法类似,但查找方向相反。

4.4.13 pollFirst() 方法

pollFirst() 方法用于移除并返回 TreeSet 中的第一个元素。以下是相关源码及注释:

// TreeSet 中的 pollFirst 方法,移除并返回第一个元素
public E pollFirst() {
    // 调用 TreeMap 的 pollFirstEntry 方法移除并返回第一个键值对
    Map.Entry<E,?> e = m.pollFirstEntry();
    // 如果键值对不为空,返回键;否则返回 null
    return (e == null)? null : e.getKey();
}

// TreeMap 中的 pollFirstEntry 方法
public Map.Entry<K,V> pollFirstEntry() {
    // 获取第一个节点
    Entry<K,V> p = firstEntry();
    // 如果第一个节点不为空
    if (p != null) {
        // 移除第一个节点
        K key = p.key;
        V value = p.value;
        deleteEntry(p);
        // 返回第一个键值对
        return new AbstractMap.SimpleImmutableEntry<>(key, value);
    }
    // 第一个节点为空,返回 null
    return null;
}

// TreeMap 中的 firstEntry 方法
final Entry<K,V> firstEntry() {
    // 获取根节点
    Entry<K,V> p = root;
    // 如果根节点不为空
    if (p != null)
        // 向左子树遍历,直到找到最左边的节点
        while (p.left != null)
            p = p.left;
    // 返回最左边的节点
    return p;
}

pollFirst 方法中,TreeSet 调用 TreeMappollFirstEntry 方法。TreeMappollFirstEntry 方法会先找到第一个节点,然后移除该节点并返回对应的键值对。如果第一个节点为空,则返回 null

4.4.14 pollLast() 方法

pollLast() 方法用于移除并返回 TreeSet 中的最后一个元素。以下是相关源码及注释:

// TreeSet 中的 pollLast 方法,移除并返回最后一个元素
public E pollLast() {
    // 调用 TreeMap 的 pollLastEntry 方法移除并返回最后一个键值对
    Map.Entry<E,?> e = m.pollLastEntry();
    // 如果键值对不为空,返回键;否则返回 null
    return (e == null)? null : e.getKey();
}

// TreeMap 中的 pollLastEntry 方法
public Map.Entry<K,V> pollLastEntry() {
    // 获取最后一个节点
    Entry<K,V> p = lastEntry();
    // 如果最后一个节点不为空
    if (p != null) {
        // 移除最后一个节点
        K key = p.key;
        V value = p.value;
        deleteEntry(p);
        // 返回最后一个键值对
        return new AbstractMap.SimpleImmutableEntry<>(key, value);
    }
    // 最后一个节点为空,返回 null
    return null;
}

// TreeMap 中的 lastEntry 方法
final Entry<K,V> lastEntry() {
    // 获取根节点
    Entry<K,V> p = root;
    // 如果根节点不为空
    if (p != null)
        // 向右子树遍历,直到找到最右边的节点
        while (p.right != null)
            p = p.right;
    // 返回最右边的节点
    return p;
}

pollLast 方法中,TreeSet 调用 TreeMappollLastEntry 方法。TreeMappollLastEntry 方法会先找到最后一个节点,然后移除该节点并返回对应的键值对。如果最后一个节点为空,则返回 null

五、性能分析

5.1 时间复杂度分析

  • 插入操作TreeSet 的插入操作 add(E e) 的时间复杂度为 O(logn)O(log n),其中 nn 是集合中元素的数量。这是因为 TreeSet 基于红黑树实现,红黑树是一种自平衡的二叉搜索树,插入元素时需要在树中找到合适的位置,然后进行插入操作,并可能需要进行树的平衡调整。由于红黑树的高度是对数级别的,所以插入操作的时间复杂度为 O(logn)O(log n)
  • 删除操作TreeSet 的删除操作 remove(Object o) 的时间复杂度同样为 O(logn)O(log n)。删除元素时,需要先在树中找到该元素对应的节点,然后进行删除操作,并可能需要进行树的平衡调整。因此,删除操作的时间复杂度也是 O(logn)O(log n)
  • 查找操作TreeSet 的查找操作 contains(Object o) 的时间复杂度为 O(logn)O(log n)。查找元素时,需要在树中根据元素的键进行比较,逐步缩小查找范围,直到找到该元素或确定该元素不存在。由于红黑树的高度是对数级别的,所以查找操作的时间复杂度为 O(logn)O(log n)
  • 遍历操作TreeSet 的遍历操作,如使用迭代器遍历,时间复杂度为 O(n)O(n),其中 nn 是集合中元素的数量。因为需要遍历树中的每个节点,所以时间复杂度与元素数量成正比。

5.2 空间复杂度分析

TreeSet 的空间复杂度为 O(n)O(n),其中 nn 是集合中元素的数量。这是因为 TreeSet 基于红黑树实现,每个元素对应树中的一个节点,树中节点的数量与元素数量相等。此外,红黑树的每个节点还需要额外的空间来存储颜色信息和指向父节点、左子节点、右子节点的指针。

5.3 与其他集合的性能比较

  • HashSet 比较HashSet 基于哈希表实现,插入、删除和查找操作的平均时间复杂度为 O(1)O(1)。而 TreeSet 基于红黑树实现,插入、删除和查找操作的时间复杂度为 O(logn)O(log n)。因此,在插入、删除和查找操作的性能上,HashSet 通常优于 TreeSet。但是,HashSet 不保证元素的顺序,而 TreeSet 会对元素进行排序,元素会按照自然顺序或指定的比较器顺序存储。
  • LinkedHashSet 比较LinkedHashSet 基于哈希表和双向链表实现,插入、删除和查找操作的平均时间复杂度为 O(1)O(1),并且它会按照元素的插入顺序存储元素。TreeSet 插入、删除和查找操作的时间复杂度为 O(logn)O(log n),但它会对元素进行排序。因此,在性能上 LinkedHashSet 通常优于 TreeSet,但在需要元素有序的场景下,TreeSet 更合适。

六、线程安全性问题

6.1 非线程安全的原因

TreeSet 是非线程安全的,这是因为它基于 TreeMap 实现,而 TreeMap 本身是非线程安全的。在多线程环境下,如果多个线程同时对 TreeSet 进行读写操作,可能会导致数据不一致、死循环等问题。例如,当一个线程正在对 TreeSet 进行插入操作时,另一个线程同时进行删除操作,可能会破坏红黑树的结构,导致树的平衡被打破,从而影响后续操作的正确性。以下是一个简单的示例代码,展示了多线程环境下使用 TreeSet 可能出现的问题:

import java.util.TreeSet;

public class TreeSetThreadSafetyExample {
    private static TreeSet<Integer> set = new TreeSet<>();

    public static void main(String[] args) {
        // 创建两个线程
        Thread thread1 = new Thread(() -> {
            for (int i = 0; i < 1000; i++) {
                set.add(i);
            }
        });

        Thread thread2 = new Thread(() -> {
            for (int i = 1000; i < 2000; i++) {
                set.add(i);
            }
        });

        // 启动线程
        thread1.start();
        thread2.start();

        try {
            // 等待线程执行完毕
            thread1.join();
            thread2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        // 打印集合大小
        System.out.println("Set size: " + set.size());
    }
}

在上述代码中,创建了两个线程,分别向 TreeSet 中添加元素。由于 TreeSet 是非线程安全的,在多线程环境下可能会出现数据丢失、树结构破坏等问题。

6.2 线程安全的替代方案

如果需要在多线程环境下使用类似 TreeSet 的功能,可以使用以下几种线程安全的替代方案:

  • Collections.synchronizedSortedSet:可以使用 Collections.synchronizedSortedSet 方法将一个非线程安全的 SortedSet 转换为线程安全的 SortedSet。以下是示例代码:
import java.util.Collections;
import java.util.TreeSet;
import java.util.SortedSet;

public class SynchronizedTreeSetExample {
    private static SortedSet<Integer> set = Collections.synchronizedSortedSet(new TreeSet<>());

    public static void main(String[] args) {
        // 创建两个线程
        Thread thread1 = new Thread(() -> {
            for (int i = 0; i < 1000; i++) {
                set.add(i);
            }
        });

        Thread thread2 = new Thread(() -> {
            for (int i = 1000; i < 2000; i++) {
                set.add(i);
            }
        });

        // 启动线程
        thread1.start();
        thread2.start();

        try {
            // 等待线程执行完毕
            thread1.join();
            thread2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        // 打印集合大小
        System.out.println("Set size: " + set.size());
    }
}

Collections.synchronizedSortedSet 方法返回的 SortedSet 是线程安全的,它通过在每个方法上使用同步锁来保证线程安全。

  • 使用并发容器:在 Java 并发包中,ConcurrentSkipListSet 是一个线程安全的有序集合,它基于跳表(Skip List)实现,插入、删除和查找操作的时间复杂度为 O(logn)O(log n),并且支持并发访问。以下是示例代码:
import java.util.concurrent.ConcurrentSkipListSet;

public class ConcurrentSkipListSetExample {
    private static ConcurrentSkipListSet<Integer> set = new ConcurrentSkipListSet<>();

    public static void main(String[] args) {
        // 创建两个线程
        Thread thread1 = new Thread(() -> {
            for (int i = 0; i < 1000; i++) {
                set.add(i);
            }
        });

        Thread thread2 = new Thread(() -> {
            for (int i = 1000; i < 2000; i++) {
                set.add(i);
            }
        });

        // 启动线程
        thread1.start();
        thread2.start();

        try {
            // 等待线程执行完毕
            thread1.join();
            thread2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        // 打印集合大小
        System.out.println("Set size: " + set.size());
    }
}

ConcurrentSkipListSet 内部使用跳表来维护元素的有序性,并且通过无锁算法来实现并发访问,性能较好。

七、序列化与反序列化

7.1 序列化机制概述

Java 的序列化机制允许将对象转换为字节流,以便可以将其存储到文件、通过网络传输或在内存中进行复制。TreeSet 实现了 Serializable 接口,这意味着它支持序列化和反序列化操作。当对 TreeSet 进行序列化时,其内部存储的元素以及相关的状态信息都会被转换为字节流;而在反序列化时,这些字节流会被还原为原始的 TreeSet 对象。

7.2 源码分析

TreeSet 类中虽然没有显式定义序列化和反序列化的方法,但它依赖于 TreeMap 的序列化和反序列化机制。因为 TreeSet 内部使用 TreeMap 来存储元素,所以实际上是对 TreeMap 进行序列化和反序列化。以下是 TreeMap 中与序列化相关的部分源码及注释:

// TreeMap 类实现了 Serializable 接口
public class TreeMap<K,V>
    extends AbstractMap<K,V>
    implements NavigableMap<K,V>, Cloneable, java.io.Serializable
{
    // 版本号,用于序列化和反序列化时的版本校验
    private static final long serialVersionUID = -2479143000061671589L;

    // 序列化方法,将 TreeMap 对象写入 ObjectOutputStream
    private void writeObject(java.io.ObjectOutputStream s)
        throws java.io.IOException {
        // 写入默认的序列化数据
        s.defaultWriteObject();

        // 写入元素数量
        s.writeInt(size);

        // 遍历 TreeMap 中的每个节点
        for (Entry<K,V> e = getFirstEntry(); e != null; e = successor(e)) {
            // 写入键
            s.writeObject(e.key);
            // 写入值
            s.writeObject(e.value);
        }
    }

    // 反序列化方法,从 ObjectInputStream 读取数据并恢复 TreeMap 对象
    private void readObject(final java.io.ObjectInputStream s)
        throws java.io.IOException, ClassNotFoundException {
        // 读取默认的序列化数据
        s.defaultReadObject();

        // 读取元素数量
        int size = s.readInt();

        // 初始化 TreeMap
        buildFromSorted(size, s, null, null);
    }

    // 从有序数据构建 TreeMap 的方法
    private void buildFromSorted(int size, java.io.ObjectInputStream str,
                                 V defaultVal, Comparator<? super K> c)
        throws  java.io.IOException, ClassNotFoundException {
        // 初始化元素数量
        this.size = size;
        // 初始化比较器
        comparator = c;
        // 创建根节点
        root = buildFromSorted(0, 0, size-1, computeRedLevel(size),
                               str, defaultVal);
    }

    // 递归构建红黑树的方法
    private final Entry<K,V> buildFromSorted(int level, int lo, int hi,
                                             int redLevel,
                                             java.io.ObjectInputStream str,
                                             V defaultVal)
        throws  java.io.IOException, ClassNotFoundException {
        // 如果 lo 大于 hi,返回 null
        if (hi < lo) return null;

        // 计算中间位置
        int mid = (lo + hi) >>> 1;

        // 递归构建左子树
        Entry<K,V> left  = null;
        if (lo < mid)
            left = buildFromSorted(level+1, lo, mid - 1, redLevel,
                                   str, defaultVal);

        // 读取键
        K key = (K) str.readObject();
        // 读取值
        V value = (defaultVal != null ? defaultVal : (V) str.readObject());
        // 创建新节点
        Entry<K,V> middle =  new Entry<>(key, value, null);

        // 如果当前节点的层级等于红色层级,将节点颜色设置为红色
        if (level == redLevel)
            middle.color = RED;

        // 将左子树连接到中间节点
        if (left != null) {
            middle.left = left;
            left.parent = middle;
        }

        // 递归构建右子树
        if (mid < hi) {
            Entry<K,V> right = buildFromSorted(level+1, mid+1, hi, redLevel,
                                               str, defaultVal);
            // 将右子树连接到中间节点
            middle.right = right;
            right.parent = middle;
        }

        // 返回中间节点
        return middle;
    }

    // 计算红色层级的方法
    private static int computeRedLevel(int sz) {
        int level = 0;
        for (int m = sz - 1; m >= 0; m = m / 2 - 1)
            level++;
        return level;
    }
}
序列化过程

writeObject 方法中,首先调用 s.defaultWriteObject() 写入默认的序列化数据,然后写入元素数量。接着,通过遍历 TreeMap 中的每个节点,依次写入键和值。这样,TreeSet 内部存储的元素信息就被完整地写入到字节流中。

反序列化过程

readObject 方法中,首先调用 s.defaultReadObject() 读取默认的序列化数据,然后读取元素数量。接着调用 buildFromSorted 方法从有序数据构建 TreeMapbuildFromSorted 方法会递归地构建红黑树,通过读取键和值创建节点,并根据节点的层级设置节点的颜色。

7.3 注意事项

  • 元素的可序列化性TreeSet 中的元素必须实现 Serializable 接口,否则在序列化 TreeSet 时会抛出 NotSerializableException 异常。因为在序列化 TreeSet 时,会将内部存储的元素也进行序列化。
  • 版本兼容性TreeMap 中定义了 serialVersionUID,用于序列化和反序列化时的版本校验。如果在序列化和反序列化时 serialVersionUID 不一致,可能会导致反序列化失败。因此,在对 TreeSet 进行序列化和反序列化时,要确保类的版本一致。

八、使用场景与示例

8.1 常见使用场景

  • 需要有序集合的场景:当需要对元素进行排序并存储时,TreeSet 是一个很好的选择。例如,在一个学生成绩管理系统中,需要按照学生的成绩对学生进行排序,可以使用 TreeSet 来存储学生对象,并重写学生类的 compareTo 方法或提供一个自定义的比较器来实现排序。
  • 去重并排序的场景TreeSet 不允许存储重复的元素,并且会对元素进行排序。因此,当需要对一组数据进行去重并排序时,可以使用 TreeSet。例如,从一个文件中读取一组数字,需要去除重复的数字并按照从小到大的顺序排列,就可以使用 TreeSet 来实现。
  • 范围查找的场景TreeSet 提供了一系列导航方法,如 lowerfloorceilinghigher 等,可以方便地进行范围查找。例如,在一个电商系统中,需要查找价格在某个范围内的商品,可以使用 TreeSet 存储商品对象,并根据商品的价格进行排序,然后使用导航方法来查找符合条件的商品。

8.2 示例代码

8.2.1 存储学生对象并排序
import java.util.TreeSet;

// 学生类,实现 Comparable 接口
class Student implements Comparable<Student> {
    private String name;
    private int score;

    public Student(String name, int score) {
        this.name = name;
        this.score = score;
    }

    public String getName() {
        return name;
    }

    public int getScore() {
        return score;
    }

    // 重写 compareTo 方法,按照成绩从高到低排序
    @Override
    public int compareTo(Student other) {
        return Integer.compare(other.score, this.score);
    }

    @Override
    public String toString() {
        return "Student{name='" + name + "', score=" + score + "}";
    }
}

public class StudentTreeSetExample {
    public static void main(String[] args) {
        // 创建 TreeSet 存储学生对象
        TreeSet<Student> studentSet = new TreeSet<>();

        // 添加学生对象
        studentSet.add(new Student("Alice", 85));
        studentSet.add(new Student("Bob", 90));
        studentSet.add(new Student("Charlie", 78));

        // 遍历 TreeSet 并打印学生信息
        for (Student student : studentSet) {
            System.out.println(student);
        }
    }
}

在上述代码中,定义了一个 Student 类,实现了 Comparable 接口,并重写了 compareTo 方法,按照成绩从高到低排序。然后创建了一个 TreeSet 来存储学生对象,添加了几个学生对象后,遍历 TreeSet 并打印学生信息。

8.2.2 去重并排序数字
import java.util.TreeSet;

public class NumberTreeSetExample {
    public static void main(String[] args) {
        // 创建 TreeSet 存储数字
        TreeSet<Integer> numberSet = new TreeSet<>();

        // 添加数字
        numberSet.add(5);
        numberSet.add(3);
        numberSet.add(7);
        numberSet.add(3);
        numberSet.add(9);

        // 遍历 TreeSet 并打印数字
        for (int number : numberSet) {
            System.out.println(number);
        }
    }
}

在上述代码中,创建了一个 TreeSet 来存储数字,添加了几个数字,其中包含重复的数字。由于 TreeSet 不允许存储重复的元素,所以重复的数字会被自动去除。最后遍历 TreeSet 并打印数字,输出的数字是按照从小到大的顺序排列的。

8.2.3 范围查找商品
import java.util.TreeSet;

// 商品类,实现 Comparable 接口
class Product implements Comparable<Product> {
    private String name;
    private double price;

    public Product(String name, double price) {
        this.name = name;
        this.price = price;
    }

    public String getName() {
        return name;
    }

    public double getPrice() {
        return price;
    }

    // 重写 compareTo 方法,按照价格从小到大排序
    @Override
    public int compareTo(Product other) {
        return Double.compare(this.price, other.price);
    }

    @Override
    public String toString() {
        return "Product{name='" + name + "', price=" + price + "}";
    }
}

public class ProductTreeSetExample {
    public static void main(String[] args) {
        // 创建 TreeSet 存储商品对象
        TreeSet<Product> productSet = new TreeSet<>();

        // 添加商品对象
        productSet.add(new Product("Apple", 5.0));
        productSet.add(new Product("Banana", 3.0));
        productSet.add(new Product("Cherry", 8.0));
        productSet.add(new Product("Date", 6.0));

        // 查找价格在 4 到 7 之间的商品
        Product fromProduct = new Product("", 4.0);
        Product toProduct = new Product("", 7.0);
        TreeSet<Product> rangeSet = (TreeSet<Product>) productSet.subSet(fromProduct, true, toProduct, true);

        // 遍历范围集合并打印商品信息
        for (Product product : rangeSet) {
            System.out.println(product);
        }
    }
}

在上述代码中,定义了一个 Product 类,实现了 Comparable 接口,并重写了 compareTo 方法,按照价格从小到大排序。然后创建了一个 TreeSet 来存储商品对象,添加了几个商品对象。接着使用 subSet 方法查找价格在 4 到 7 之间的商品,并将结果存储在一个新的 TreeSet 中。最后遍历这个范围集合并打印商品信息。

九、总结与展望

9.1 总结

通过对 TreeSet 的源码分析,我们深入了解了其内部结构、基本操作的实现原理、性能特点、线程安全性问题以及使用场景。TreeSet 基于红黑树实现,它会对元素进行排序,不允许存储重复的元素。其插入、删除和查找操作的时间复杂度为 O(logn)O(log n),空间复杂度为 O(n)O(n)。由于 TreeSet 是非线程安全的,在多线程环境下需要使用线程安全的替代方案。

9.2 展望

随着 Java 技术的不断发展,TreeSet 可能会在以下方面得到进一步的优化和改进:

  • 性能优化:尽管红黑树已经是一种高效的数据结构,但在某些特定场景下,可能会有更高效的替代方案。未来可能会对 TreeSet 的内部实现进行优化,以提高其在大规模数据处理时的性能。
  • 并发性能提升:在多线程环境下,现有的线程安全替代方案可能会存在性能瓶颈。未来可能会引入更高效的并发算法,以提升 TreeSet 在并发场景下的性能。
  • 功能扩展:可能会为 TreeSet 增加更多的功能,例如支持更复杂的排序规则、提供更丰富的导航方法等,以满足不同用户的需求。

总之,TreeSet 作为 Java 集合框架中的一个重要组成部分,在有序集合的应用场景中发挥着重要作用。随着技术的不断进步,TreeSet 有望在性能和功能上得到进一步的提升,为开发者提供更好的使用体验。