深度剖析 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 与其他集合的对比
与其他常见的集合实现(如 HashSet 和 LinkedHashSet)相比,TreeSet 具有以下特点:
- 不允许重复元素:和
HashSet、LinkedHashSet一样,TreeSet不允许存储重复的元素,当尝试添加重复元素时,添加操作将失败。 - 有序性:
TreeSet会对元素进行排序,元素会按照自然顺序(元素实现Comparable接口)或者指定的比较器(Comparator)顺序进行存储。而HashSet不保证元素的存储顺序,LinkedHashSet则按照元素的插入顺序进行存储。 - 性能特点:
TreeSet的插入、删除和查找操作的时间复杂度为 ,其中 是集合中元素的数量。这是因为红黑树的高度是对数级别的。而HashSet和LinkedHashSet的插入、删除和查找操作的平均时间复杂度为 。
三、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 实例,并将其赋值给成员变量 m。TreeMap 是基于红黑树实现的,它会根据元素的自然顺序或者指定的比较器顺序对元素进行排序。
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 来存储元素,实际上调用的是 TreeMap 的 put 方法。以下是相关的源码及注释:
// 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 方法中,首先调用 TreeMap 的 put 方法,将元素作为键,PRESENT 作为值插入到 TreeMap 中。在 put 方法中,会先判断根节点是否为空,如果为空则创建一个新的根节点。然后根据比较器(如果有)或元素的自然顺序查找插入位置,若找到相同键的节点,则更新其值并返回旧值;若未找到,则创建一个新节点并插入到合适的位置。插入后会调用 fixAfterInsertion 方法进行红黑树的平衡调整,以保证红黑树的性质。
4.1.2 addAll(Collection<? extends E> c) 方法
addAll(Collection<? extends E> c) 方法用于将指定集合中的所有元素添加到 TreeSet 中。它调用的是 AbstractCollection 的 addAll 方法,而 AbstractCollection 的 addAll 方法会遍历指定集合中的每个元素,调用 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 方法中,首先判断是否满足使用线性时间版本的条件,如果满足则调用 TreeMap 的 addAllForTreeSet 方法进行批量添加;否则调用父类 AbstractCollection 的 addAll 方法,该方法会遍历指定集合中的每个元素,调用 add 方法将元素添加到 TreeSet 中。
4.2 删除操作
4.2.1 remove(Object o) 方法
remove(Object o) 方法用于从 TreeSet 中移除指定的元素。它调用的是 TreeMap 的 remove 方法。以下是相关的源码及注释:
// 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 方法中,首先调用 TreeMap 的 getEntry 方法查找指定键对应的节点,如果节点存在,则调用 deleteEntry 方法移除该节点。在 deleteEntry 方法中,会根据节点的子节点情况进行不同的处理。如果节点有左右子节点,会找到其后继节点并将后继节点的键和值复制到当前节点,然后将 p 指向后继节点。接着找到替换节点,将替换节点连接到父节点,并根据节点的颜色进行红黑树的平衡调整。
4.2.2 removeAll(Collection<?> c) 方法
removeAll(Collection<?> c) 方法用于从 TreeSet 中移除指定集合中包含的所有元素。它调用的是 AbstractSet 的 removeAll 方法。以下是相关的源码及注释:
// 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 中的所有元素。它调用的是 TreeMap 的 clear 方法。以下是相关的源码及注释:
// TreeSet 中的 clear 方法,用于清空 TreeSet 中的所有元素
public void clear() {
// 调用 TreeMap 的 clear 方法,清空 TreeMap 中的所有键值对
m.clear();
}
// TreeMap 中的 clear 方法
public void clear() {
// 记录结构修改次数
modCount++;
// 元素数量置为 0
size = 0;
// 根节点置为 null
root = null;
}
在 clear 方法中,调用 TreeMap 的 clear 方法,将 TreeMap 的根节点置为 null,元素数量置为 0,并记录结构修改次数。
4.3 查找操作
4.3.1 contains(Object o) 方法
contains(Object o) 方法用于检查 TreeSet 中是否包含指定的元素。它调用的是 TreeMap 的 containsKey 方法。以下是相关的源码及注释:
// 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 方法中,调用 TreeMap 的 containsKey 方法,该方法又调用 getEntry 方法查找指定键对应的节点。在 getEntry 方法中,会根据比较器(如果有)或元素的自然顺序查找节点,如果找到节点则返回该节点,否则返回 null。如果返回值不为 null,说明 TreeSet 中包含该元素,返回 true;否则返回 false。
4.4 其他操作
4.4.1 size() 方法
size() 方法用于返回 TreeSet 中元素的数量。它调用的是 TreeMap 的 size 方法。以下是相关的源码及注释:
// TreeSet 中的 size 方法,用于返回 TreeSet 中元素的数量
public int size() {
// 调用 TreeMap 的 size 方法,返回 TreeMap 中键值对的数量,即 TreeSet 中元素的数量
return m.size();
}
// TreeMap 中的 size 方法
public int size() {
// 返回元素数量
return size;
}
在 size 方法中,调用 TreeMap 的 size 方法,返回 TreeMap 中键值对的数量,也就是 TreeSet 中元素的数量。
4.4.2 isEmpty() 方法
isEmpty() 方法用于检查 TreeSet 是否为空。它调用的是 TreeMap 的 isEmpty 方法。以下是相关的源码及注释:
// 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 方法中,调用 TreeMap 的 isEmpty 方法,检查 TreeMap 中键值对的数量是否为 0,如果为 0 则返回 true,表示 TreeSet 为空;否则返回 false。
4.4.3 iterator() 方法
iterator() 方法用于返回一个迭代器,用于遍历 TreeSet 中的元素。由于 TreeSet 是有序的,迭代器会按照元素的排序顺序依次遍历元素。它调用的是 TreeMap 的 navigableKeySet().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 调用 TreeMap 的 navigableKeySet().iterator() 来获取迭代器。TreeMap 的 navigableKeySet() 方法返回一个 KeySet 对象,这个 KeySet 对象实现了 NavigableSet 接口,提供了一系列与导航相关的方法。KeySet 的 iterator() 方法最终会返回一个能够按照元素排序顺序遍历键集合的迭代器。
4.4.4 subSet(E fromElement, boolean fromInclusive, E toElement, boolean toInclusive) 方法
subSet(E fromElement, boolean fromInclusive, E toElement, boolean toInclusive) 方法用于返回 TreeSet 中从 fromElement 到 toElement 的子集合。这个子集合是一个视图,对它的修改会反映到原 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 调用 TreeMap 的 subMap 方法获取一个子映射,然后使用这个子映射创建一个新的 TreeSet。TreeMap 的 subMap 方法会创建一个 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 调用 TreeMap 的 headMap 方法获取一个子映射,然后使用这个子映射创建一个新的 TreeSet。TreeMap 的 headMap 方法同样会创建一个 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 调用 TreeMap 的 tailMap 方法获取一个子映射,然后使用这个子映射创建一个新的 TreeSet。TreeMap 的 tailMap 方法创建的 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 调用 TreeMap 的 firstKey 方法。TreeMap 的 firstKey 方法会从根节点开始,不断向左子树遍历,直到找到最左边的节点,该节点的键就是 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 调用 TreeMap 的 lastKey 方法。TreeMap 的 lastKey 方法会从根节点开始,不断向右子树遍历,直到找到最右边的节点,该节点的键就是 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 调用 TreeMap 的 lowerKey 方法。TreeMap 的 lowerKey 方法会调用 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 调用 TreeMap 的 floorKey 方法。TreeMap 的 floorKey 方法会调用 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 调用 TreeMap 的 ceilingKey 方法。TreeMap 的 ceilingKey 方法会调用 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 调用 TreeMap 的 higherKey 方法。TreeMap 的 higherKey 方法会调用 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 调用 TreeMap 的 pollFirstEntry 方法。TreeMap 的 pollFirstEntry 方法会先找到第一个节点,然后移除该节点并返回对应的键值对。如果第一个节点为空,则返回 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 调用 TreeMap 的 pollLastEntry 方法。TreeMap 的 pollLastEntry 方法会先找到最后一个节点,然后移除该节点并返回对应的键值对。如果最后一个节点为空,则返回 null。
五、性能分析
5.1 时间复杂度分析
- 插入操作:
TreeSet的插入操作add(E e)的时间复杂度为 ,其中 是集合中元素的数量。这是因为TreeSet基于红黑树实现,红黑树是一种自平衡的二叉搜索树,插入元素时需要在树中找到合适的位置,然后进行插入操作,并可能需要进行树的平衡调整。由于红黑树的高度是对数级别的,所以插入操作的时间复杂度为 。 - 删除操作:
TreeSet的删除操作remove(Object o)的时间复杂度同样为 。删除元素时,需要先在树中找到该元素对应的节点,然后进行删除操作,并可能需要进行树的平衡调整。因此,删除操作的时间复杂度也是 。 - 查找操作:
TreeSet的查找操作contains(Object o)的时间复杂度为 。查找元素时,需要在树中根据元素的键进行比较,逐步缩小查找范围,直到找到该元素或确定该元素不存在。由于红黑树的高度是对数级别的,所以查找操作的时间复杂度为 。 - 遍历操作:
TreeSet的遍历操作,如使用迭代器遍历,时间复杂度为 ,其中 是集合中元素的数量。因为需要遍历树中的每个节点,所以时间复杂度与元素数量成正比。
5.2 空间复杂度分析
TreeSet 的空间复杂度为 ,其中 是集合中元素的数量。这是因为 TreeSet 基于红黑树实现,每个元素对应树中的一个节点,树中节点的数量与元素数量相等。此外,红黑树的每个节点还需要额外的空间来存储颜色信息和指向父节点、左子节点、右子节点的指针。
5.3 与其他集合的性能比较
- 与
HashSet比较:HashSet基于哈希表实现,插入、删除和查找操作的平均时间复杂度为 。而TreeSet基于红黑树实现,插入、删除和查找操作的时间复杂度为 。因此,在插入、删除和查找操作的性能上,HashSet通常优于TreeSet。但是,HashSet不保证元素的顺序,而TreeSet会对元素进行排序,元素会按照自然顺序或指定的比较器顺序存储。 - 与
LinkedHashSet比较:LinkedHashSet基于哈希表和双向链表实现,插入、删除和查找操作的平均时间复杂度为 ,并且它会按照元素的插入顺序存储元素。TreeSet插入、删除和查找操作的时间复杂度为 ,但它会对元素进行排序。因此,在性能上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)实现,插入、删除和查找操作的时间复杂度为 ,并且支持并发访问。以下是示例代码:
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 方法从有序数据构建 TreeMap。buildFromSorted 方法会递归地构建红黑树,通过读取键和值创建节点,并根据节点的层级设置节点的颜色。
7.3 注意事项
- 元素的可序列化性:
TreeSet中的元素必须实现Serializable接口,否则在序列化TreeSet时会抛出NotSerializableException异常。因为在序列化TreeSet时,会将内部存储的元素也进行序列化。 - 版本兼容性:
TreeMap中定义了serialVersionUID,用于序列化和反序列化时的版本校验。如果在序列化和反序列化时serialVersionUID不一致,可能会导致反序列化失败。因此,在对TreeSet进行序列化和反序列化时,要确保类的版本一致。
八、使用场景与示例
8.1 常见使用场景
- 需要有序集合的场景:当需要对元素进行排序并存储时,
TreeSet是一个很好的选择。例如,在一个学生成绩管理系统中,需要按照学生的成绩对学生进行排序,可以使用TreeSet来存储学生对象,并重写学生类的compareTo方法或提供一个自定义的比较器来实现排序。 - 去重并排序的场景:
TreeSet不允许存储重复的元素,并且会对元素进行排序。因此,当需要对一组数据进行去重并排序时,可以使用TreeSet。例如,从一个文件中读取一组数字,需要去除重复的数字并按照从小到大的顺序排列,就可以使用TreeSet来实现。 - 范围查找的场景:
TreeSet提供了一系列导航方法,如lower、floor、ceiling、higher等,可以方便地进行范围查找。例如,在一个电商系统中,需要查找价格在某个范围内的商品,可以使用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 基于红黑树实现,它会对元素进行排序,不允许存储重复的元素。其插入、删除和查找操作的时间复杂度为 ,空间复杂度为 。由于 TreeSet 是非线程安全的,在多线程环境下需要使用线程安全的替代方案。
9.2 展望
随着 Java 技术的不断发展,TreeSet 可能会在以下方面得到进一步的优化和改进:
- 性能优化:尽管红黑树已经是一种高效的数据结构,但在某些特定场景下,可能会有更高效的替代方案。未来可能会对
TreeSet的内部实现进行优化,以提高其在大规模数据处理时的性能。 - 并发性能提升:在多线程环境下,现有的线程安全替代方案可能会存在性能瓶颈。未来可能会引入更高效的并发算法,以提升
TreeSet在并发场景下的性能。 - 功能扩展:可能会为
TreeSet增加更多的功能,例如支持更复杂的排序规则、提供更丰富的导航方法等,以满足不同用户的需求。
总之,TreeSet 作为 Java 集合框架中的一个重要组成部分,在有序集合的应用场景中发挥着重要作用。随着技术的不断进步,TreeSet 有望在性能和功能上得到进一步的提升,为开发者提供更好的使用体验。