# SortedSet 接口

> 原文： [https://docs.oracle.com/javase/tutorial/collections/interfaces/sorted-set.html](https://docs.oracle.com/javase/tutorial/collections/interfaces/sorted-set.html)

[`SortedSet`](https://docs.oracle.com/javase/8/docs/api/java/util/SortedSet.html) 是 [`Set`](https://docs.oracle.com/javase/8/docs/api/java/util/Set.html) ，按升序维持其元素，根据元素的自然顺序或根据`SortedSet`创建时提供的`Comparator`进行排序时间。除了正常的`Set`操作外，`SortedSet`接口还提供以下操作：

*   `Range view` - 允许对有序集进行任意范围操作
*   `Endpoints` - 返回有序集合中的第一个或最后一个元素
*   `Comparator access` - 返回用于对集合进行排序的`Comparator`（如果有）

接下来是`SortedSet`接口的代码。

```
public interface SortedSet<E> extends Set<E> {
    // Range-view
    SortedSet<E> subSet(E fromElement, E toElement);
    SortedSet<E> headSet(E toElement);
    SortedSet<E> tailSet(E fromElement);

    // Endpoints
    E first();
    E last();

    // Comparator access
    Comparator<? super E> comparator();
}

```

## 设置操作

`SortedSet`从`Set`继承的操作在排序集和普通集上的行为相同，但有两个例外：

*   `iterator`操作返回的`Iterator`按顺序遍历已排序的组。
*   `toArray`返回的数组按顺序包含有序集合的元素。

虽然接口不保证它，但 Java 平台的`SortedSet`实现的`toString`方法按顺序返回包含有序集合的所有元素的字符串。

按照惯例，所有通用`Collection`实现都提供了一个标准转换构造函数，它采用`Collection`; `SortedSet`实现也不例外。在`TreeSet`中，此构造函数创建一个实例，根据其自然顺序对其元素进行排序。这可能是一个错误。最好动态检查以查看指定的集合是否是`SortedSet`实例，如果是，则根据相同的标准（比较器或自然排序）对新的`TreeSet`进行排序。因为`TreeSet`采用了它所采用的方法，它还提供了一个构造函数，该构造函数接受`SortedSet`并返回包含根据相同标准排序的相同元素的新`TreeSet`。请注意，它是参数的编译时类型，而不是其运行时类型，它确定调用这两个构造函数中的哪一个（以及是否保留了排序条件）。

按照惯例，`SortedSet`实现还提供了一个构造函数，它接受`Comparator`并返回根据指定的`Comparator`排序的空集。如果`null`传递给此构造函数，它将返回一个集合，该集合根据其自然顺序对其元素进行排序。

## 范围视图操作

`range-view`操作有点类似于`List`接口提供的操作，但有一个很大的区别。即使直接修改了后备排序集，排序集的范围视图仍然有效。这是可行的，因为有序集的范围视图的端点是元素空间中的绝对点，而不是后备集合中的特定元素，如列表的情况。有序集合的`range-view`实际上只是集合的任何部分位于元素空间的指定部分的窗口。对`range-view`的更改会写回后退排序集，反之亦然。因此，与列表上的`range-view`不同，可以长时间在排序集上使用`range-view`。

排序集提供三个`range-view`操作。第一个`subSet`采用两个端点，如`subList`。而不是索引，端点是对象，必须与有序集合中的元素相比较，使用`Set`的`Comparator`或其元素的自然顺序，无论`Set`用于自我排序。与`subList`类似，范围是半开放的，包括其低端点但不包括高端点。

因此，以下代码行告诉您`"doorbell"`和`"pickle"`之间有多少字，包括`"doorbell"`但不包括`"pickle"`，包含在称为`dictionary`的字符串的`SortedSet`中：

```
int count = dictionary.subSet("doorbell", "pickle").size();

```

以类似的方式，以下单行删除以字母`f`开头的所有元素。

```
dictionary.subSet("f", "g").clear();

```

类似的技巧可以用来打印一个表格，告诉你每个字母开头有多少个单词。

```
for (char ch = 'a'; ch <= 'z'; ) {
    String from = String.valueOf(ch++);
    String to = String.valueOf(ch);
    System.out.println(from + ": " + dictionary.subSet(from, to).size());
}

```

假设您要查看 _ 关闭区间 _，它包含两个端点，而不是开放区间。如果元素类型允许计算元素空间中给定值的后继，则仅从`lowEndpoint`请求`subSet`到`successor(highEndpoint)`。虽然不完全明显，但`String`自然排序中字符串`s`的后继是`s + "\0"` - 即附加了`null`字符的`s`。

因此，下面的单线程告诉您`"doorbell"`和`"pickle"`之间有多少单词，包括门铃 _ 和 _ pickle，都包含在字典中。

```
count = dictionary.subSet("doorbell", "pickle\0").size();

```

可以使用类似的技术来查看 _ 开放区间 _，其中既不包含端点也不包含端点。从`lowEndpoint`到`highEndpoint`的开路间隔视图是从`successor(lowEndpoint)`到`highEndpoint`的半开间隔。使用以下内容计算`"doorbell"`和`"pickle"`之间的字数，不包括两者。

```
count = dictionary.subSet("doorbell\0", "pickle").size();

```

`SortedSet`接口包含两个`range-view`操作 - `headSet`和`tailSet`，两者都采用单个`Object`参数。前者返回后备`SortedSet`的初始部分的视图，最多但不包括指定的对象。后者返回背景`SortedSet`的最后部分的视图，从指定对象开始并继续到背景`SortedSet`的末尾。因此，以下代码允许您将字典视为两个不相交的`volumes`（`a-m`和`n-z`）。

```
SortedSet<String> volume1 = dictionary.headSet("n");
SortedSet<String> volume2 = dictionary.tailSet("n");

```

## 端点操作

`SortedSet`接口包含返回有序集合中第一个和最后一个元素的操作，毫不奇怪地称为`first`和`last`。除了明显的用途之外，`last`还允许解决`SortedSet`接口缺陷的问题。你想用`SortedSet`做的一件事就是进入`Set`的内部并向前或向后迭代。从内部向前行很容易：只需得到一个`tailSet`并迭代它。不幸的是，没有简单的方法可以倒退。

以下习语获得的元素空间中的第一个元素小于指定对象`o`。

```
Object predecessor = ss.headSet(o).last();

```

这是从排序集内部的一个点向后移动一个元素的好方法。它可以重复应用以向后迭代，但这是非常低效的，需要查找返回的每个元素。

## 比较器访问器

`SortedSet`接口包含一个名为`comparator`的访问器方法，它返回用于对集合进行排序的`Comparator`，如果该集合根据其元素的 _ 自然顺序 _ 进行排序，则返回`null`。提供此方法以便可以将排序的集合复制到具有相同排序的新排序集合中。它由[之前描述的`SortedSet`构造函数](#constructor)使用。