Java NavigableSet接口从入门到实战详解
作者:白山编程大哥
1. 引言
在 Java 集合框架中,Set 接口用于存储不重复的元素,而 SortedSet 接口则在 Set 的基础上增加了元素的排序能力。NavigableSet 接口继承自 SortedSet,进一步扩展了导航方法,使得开发者可以更方便地获取集合中与目标元素最接近的元素,例如获取小于某个值的最大元素、大于某个值的最小元素等。
本文将深入讲解 NavigableSet 接口的定义、常用实现类、核心方法以及实际应用场景,帮助你在日常开发中更高效地使用这一强大的集合工具。
2. NavigableSet 接口概述
2.1 接口定义
NavigableSet 位于 java.util 包中,其接口定义如下:
public interface NavigableSet<E> extends SortedSet<E> {
// 导航方法
E lower(E e);
E floor(E e);
E ceiling(E e);
E higher(E e);
// 获取子集合视图
NavigableSet<E> descendingSet();
NavigableSet<E> subSet(E fromElement, boolean fromInclusive,
E toElement, boolean toInclusive);
NavigableSet<E> headSet(E toElement, boolean inclusive);
NavigableSet<E> tailSet(E fromElement, boolean inclusive);
// 获取并移除元素
E pollFirst();
E pollLast();
// 迭代器
Iterator<E> descendingIterator();
}
2.2 与 SortedSet 的关系
NavigableSet 继承自 SortedSet,因此它拥有 SortedSet 的所有方法,如 first()、last()、comparator() 等。在此基础上,NavigableSet 增加了更灵活的导航查询和子集合视图操作。
2.3 主要实现类
NavigableSet 接口最常用的实现类是 TreeSet。TreeSet 基于红黑树实现,能够保证元素处于有序状态,并且插入、删除、查找操作的时间复杂度均为 O(log n)。
NavigableSet<Integer> set = new TreeSet<>();
3. 核心导航方法详解
NavigableSet 提供了四个核心的导航查询方法,用于查找与目标元素最接近的元素。下面通过示例逐一说明。
3.1 lower(E e)
返回集合中严格小于 e 的最大元素;如果不存在,则返回 null。
NavigableSet<Integer> set = new TreeSet<>(); set.addAll(Arrays.asList(10, 20, 30, 40, 50)); // 小于 30 的最大元素是 20 Integer result = set.lower(30); System.out.println(result); // 输出:20 // 小于 10 的元素不存在,返回 null System.out.println(set.lower(10)); // 输出:null
3.2 floor(E e)
返回集合中小于等于 e 的最大元素;如果不存在,则返回 null。
// 小于等于 30 的最大元素是 30 本身 Integer result = set.floor(30); System.out.println(result); // 输出:30 // 小于等于 25 的最大元素是 20 System.out.println(set.floor(25)); // 输出:20
3.3 ceiling(E e)
返回集合中大于等于 e 的最小元素;如果不存在,则返回 null。
// 大于等于 30 的最小元素是 30 本身 Integer result = set.ceiling(30); System.out.println(result); // 输出:30 // 大于等于 35 的最小元素是 40 System.out.println(set.ceiling(35)); // 输出:40
3.4 higher(E e)
返回集合中严格大于 e 的最小元素;如果不存在,则返回 null。
// 大于 30 的最小元素是 40 Integer result = set.higher(30); System.out.println(result); // 输出:40 // 大于 50 的元素不存在,返回 null System.out.println(set.higher(50)); // 输出:null
3.5 方法对比小结
为了更直观地理解这四个方法的区别,可以参考下表:
| 方法 | 条件 | 示例(集合含 10, 20, 30, 40, 50) | 查询 30 的结果 |
|---|---|---|---|
lower(e) | 严格小于 e 的最大元素 | lower(30) | 20 |
floor(e) | 小于等于 e 的最大元素 | floor(30) | 30 |
ceiling(e) | 大于等于 e 的最小元素 | ceiling(30) | 30 |
higher(e) | 严格大于 e 的最小元素 | higher(30) | 40 |
4. 子集合视图操作
NavigableSet 提供了三个获取子集合视图的方法,并且允许通过布尔参数控制边界是否包含。
4.1 subSet
返回从 fromElement 到 toElement 之间的子集合视图,两个边界是否包含由布尔参数决定。
NavigableSet<Integer> set = new TreeSet<>(); set.addAll(Arrays.asList(10, 20, 30, 40, 50)); // 包含 20,不包含 40 NavigableSet<Integer> sub = set.subSet(20, true, 40, false); System.out.println(sub); // 输出:[20, 30]
4.2 headSet
返回小于(或小于等于)toElement 的子集合视图。
// 小于 40 的元素(不包含 40) NavigableSet<Integer> head = set.headSet(40, false); System.out.println(head); // 输出:[10, 20, 30] // 小于等于 40 的元素(包含 40) NavigableSet<Integer> headInclusive = set.headSet(40, true); System.out.println(headInclusive); // 输出:[10, 20, 30, 40]
4.3 tailSet
返回大于(或大于等于)fromElement 的子集合视图。
// 大于等于 30 的元素(包含 30) NavigableSet<Integer> tail = set.tailSet(30, true); System.out.println(tail); // 输出:[30, 40, 50] // 大于 30 的元素(不包含 30) NavigableSet<Integer> tailExclusive = set.tailSet(30, false); System.out.println(tailExclusive); // 输出:[40, 50]
4.4 视图与原始集合的关系
需要注意的是,这些子集合视图是原集合的动态视图,对视图的修改会直接反映到原集合中,反之亦然。
NavigableSet<Integer> set = new TreeSet<>(); set.addAll(Arrays.asList(10, 20, 30, 40, 50)); NavigableSet<Integer> head = set.headSet(40, false); head.add(15); // 向视图添加元素 System.out.println(set); // 输出:[10, 15, 20, 30, 40, 50]
5. 获取与移除元素
NavigableSet 提供了 pollFirst() 和 pollLast() 方法,用于获取并移除集合中的最小和最大元素。
NavigableSet<Integer> set = new TreeSet<>(); set.addAll(Arrays.asList(10, 20, 30, 40, 50)); // 获取并移除最小元素 Integer first = set.pollFirst(); System.out.println(first); // 输出:10 System.out.println(set); // 输出:[20, 30, 40, 50] // 获取并移除最大元素 Integer last = set.pollLast(); System.out.println(last); // 输出:50 System.out.println(set); // 输出:[20, 30, 40]
当集合为空时,pollFirst() 和 pollLast() 返回 null,不会抛出异常。
6. 反向视图与迭代
6.1 descendingSet()
返回该集合的逆序视图,可以用于反向遍历或查询。
NavigableSet<Integer> set = new TreeSet<>(); set.addAll(Arrays.asList(10, 20, 30, 40, 50)); NavigableSet<Integer> desc = set.descendingSet(); System.out.println(desc); // 输出:[50, 40, 30, 20, 10]
6.2 descendingIterator()
返回一个逆序迭代器,用于从大到小遍历集合。
NavigableSet<Integer> set = new TreeSet<>();
set.addAll(Arrays.asList(10, 20, 30, 40, 50));
Iterator<Integer> it = set.descendingIterator();
while (it.hasNext()) {
System.out.print(it.next() + " ");
}
// 输出:50 40 30 20 10
7. 实际应用场景
7.1 实现最近邻查找
NavigableSet 非常适合实现「查找与目标值最接近的元素」这类需求。例如,在一个已排序的分数集合中,查找某个分数附近的学生成绩:
NavigableSet<Integer> scores = new TreeSet<>();
scores.addAll(Arrays.asList(58, 62, 75, 88, 93, 100));
int target = 80;
// 小于等于 80 的最高分
Integer lowerOrEqual = scores.floor(target);
// 大于等于 80 的最低分
Integer higherOrEqual = scores.ceiling(target);
System.out.println("目标分数:" + target);
System.out.println("不高于目标的最大分数:" + lowerOrEqual);
System.out.println("不低于目标的最小分数:" + higherOrEqual);
7.2 实现区间查询
结合 subSet 方法,可以方便地实现区间查询。例如,查询成绩在 60 到 90 之间的所有学生:
NavigableSet<Integer> scores = new TreeSet<>();
scores.addAll(Arrays.asList(58, 62, 75, 88, 93, 100));
// 查询 [60, 90] 区间内的分数
NavigableSet<Integer> range = scores.subSet(60, true, 90, true);
System.out.println("60 到 90 之间的分数:" + range); // 输出:[62, 75, 88]
7.3 实现任务调度
NavigableSet 也可以用于简单的任务调度场景,例如按照优先级依次取出待处理的任务:
class Task implements Comparable<Task> {
private int priority;
private String name;
public Task(int priority, String name) {
this.priority = priority;
this.name = name;
}
@Override
public int compareTo(Task other) {
return Integer.compare(this.priority, other.priority);
}
@Override
public String toString() {
return "Task{" + "priority=" + priority + ", name='" + name + "'}";
}
}
NavigableSet<Task> taskQueue = new TreeSet<>();
taskQueue.add(new Task(3, "写代码"));
taskQueue.add(new Task(1, "修复 Bug"));
taskQueue.add(new Task(2, "代码评审"));
// 依次取出优先级最高的任务
while (!taskQueue.isEmpty()) {
Task task = taskQueue.pollFirst();
System.out.println("正在处理:" + task);
}
8. 使用注意事项
8.1 元素必须可比较
NavigableSet 要求元素要么实现了 Comparable 接口,要么在创建集合时传入 Comparator 比较器。否则在添加元素时会抛出 ClassCastException。
// 方式一:元素实现 Comparable NavigableSet<Integer> set1 = new TreeSet<>(); // 方式二:传入 Comparator NavigableSet<String> set2 = new TreeSet<>(Comparator.reverseOrder());
8.2 不允许 null 元素
TreeSet 不允许添加 null 元素,因为无法对 null 进行排序比较。如果尝试添加 null,会抛出 NullPointerException。
8.3 非线程安全
TreeSet 不是线程安全的。如果在多线程环境下使用,需要通过 Collections.synchronizedSortedSet 进行包装,或者使用并发集合类。
SortedSet<Integer> syncSet = Collections.synchronizedSortedSet(new TreeSet<>());
8.4 视图修改的边界限制
通过 subSet、headSet、tailSet 获取的视图,在添加元素时不能超出视图的边界范围,否则会抛出 IllegalArgumentException。
NavigableSet<Integer> set = new TreeSet<>(); set.addAll(Arrays.asList(10, 20, 30, 40, 50)); NavigableSet<Integer> head = set.headSet(40, false); // head.add(45); // 抛出 IllegalArgumentException,因为 45 超出视图边界
9. 总结
NavigableSet 接口在 SortedSet 的基础上提供了丰富的导航方法,使得开发者可以轻松实现最近邻查找、区间查询、双向遍历等操作。其核心实现类 TreeSet 基于红黑树,保证了良好的性能表现。
在实际开发中,当你需要维护一个有序且不重复的元素集合,并且需要频繁进行范围查询或获取最接近元素的操作时,NavigableSet 是一个非常合适的选择。掌握它的核心方法,能够帮助你写出更简洁、高效的代码。
以上就是Java NavigableSet接口从入门到实战详解的详细内容,更多关于Java NavigableSet接口详解的资料请关注脚本之家其它相关文章!
