307. Java Stream API - 排序流

40 阅读3分钟

307. Java Stream API - 排序流

在 Java 中,SORTED 流表示元素已经按某种规则排序。这些流可以通过两种方式创建:

  1. 排序的源:例如 TreeSet,它自然保持元素的顺序。
  2. 调用 sorted() 方法:该方法可以将流中的元素按自然顺序或自定义的比较器进行排序。

知道流是已排序的,可以帮助流的实现做一些优化。例如,当流已经是排序好的时,流的实现可以跳过再次排序的步骤,从而提高效率。然而,流的排序特性并非在所有情况下都能被优化。因为有时候,如果使用不同的比较器对流进行排序,排序特性可能会丧失。

SORTED 特性的清除

某些中间操作会移除流的 SORTED 特性。例如,当你使用 map()flatMap() 等操作时,即使流之前是排序过的,结果流将不再被视为 SORTED 流。

示例:检查一个流是否是 SORTED

你可以通过 Spliterator 接口中的 characteristics() 方法来检查流是否具有 SORTED 特性。下面是一个简单的例子,展示如何检查流的排序特性:

Predicate<Stream<?>> isSorted = 
        stream -> ((stream.spliterator().characteristics() & Spliterator.SORTED) != 0);

List<String> strings = List.of("one", "two", "two", "three", "four", "five");

System.out.println("Is strings sorted? " + isSorted.test(strings.stream())); // false
Stream<String> sortedStrings = strings.stream().sorted();
System.out.println("Is sortedStrings sorted? " + isSorted.test(sortedStrings)); // true
Stream<String> filtered = strings.stream().sorted().filter(s -> s.length() < 5);
System.out.println("Is filtered sorted? " + isSorted.test(filtered)); // true
Stream<Integer> lengths = strings.stream().sorted().filter(s -> s.length() < 5).map(String::length);
System.out.println("Is lengths sorted? " + isSorted.test(lengths)); // false

解释:

  • strings.stream() 返回的流没有排序,所以 isSorted.test(strings.stream()) 返回 false
  • sortedStrings 是通过调用 sorted() 创建的流,它是有序的,因此 isSorted.test(sortedStrings) 返回 true
  • filtered 流是对 sortedStrings 进行过滤后的结果,它依然保持排序特性,因为过滤操作不会影响排序。
  • lengths 流通过 map() 转换了元素(将字符串转换为其长度),这会导致移除 SORTED 特性,因此 isSorted.test(lengths) 返回 false
输出:
Is strings sorted? false
Is sortedStrings sorted? true
Is filtered sorted? true
Is lengths sorted? false

✅ 排序流的创建

通过 sorted() 创建排序流

sorted() 方法可以创建一个有序的流,默认使用元素的自然顺序进行排序,也可以通过传入一个自定义的比较器来指定排序方式:

示例 1:使用自然顺序排序
Stream<String> sortedStream = Stream.of("one", "two", "three", "four").sorted();
sortedStream.forEach(System.out::println);
输出:
four
one
three
two
示例 2:使用自定义比较器进行排序
Stream<String> sortedStream = Stream.of("one", "two", "three", "four")
    .sorted(Comparator.comparingInt(String::length)); // 按字符串长度排序
sortedStream.forEach(System.out::println);
输出:
one
two
four
three

通过 TreeSet 创建排序流

TreeSet 是一种排序的集合,它会自动根据元素的自然顺序进行排序,或者你可以提供一个自定义的比较器:

Set<String> sortedSet = new TreeSet<>(Comparator.comparingInt(String::length));
sortedSet.addAll(List.of("one", "two", "three", "four"));
sortedSet.forEach(System.out::println);
输出:
one
two
four
three

✅ 流的排序特性清除

某些操作会移除流的 SORTED 特性,特别是当你进行元素转换或映射时。例如,map()flatMap() 操作都会导致排序特性丧失。

示例:排序特性丧失

Stream<String> sortedStream = Stream.of("apple", "banana", "cherry").sorted();
Stream<Integer> mappedStream = sortedStream.map(String::length); // map 操作移除了排序特性
boolean isSortedAfterMap = isSorted.test(mappedStream);
System.out.println("Is mappedStream sorted? " + isSortedAfterMap); // false
输出:
Is mappedStream sorted? false

通过 map() 操作,排序特性被移除,因为 map() 可能会修改元素的顺序。


✅ 总结

  • 排序流是具有 SORTED 特性的流,它可以通过 TreeSet 等有序数据源或调用 sorted() 方法创建。
  • 一些中间操作,如 map()flatMap(),会移除 SORTED 特性。
  • 知道一个流是否已排序,能够帮助我们做出更高效的并行流处理决策,特别是在需要避免不必要的排序时。

通过掌握如何使用和检查流的排序特性,可以让我们编写更加高效的流操作,避免不必要的性能开销。