Java Streams:如何做一个有效的“独特和排序”?

让我们假设我有一个Stream并希望只获得不同的元素并进行排序。

天真的做法是做到以下几点:

 Stream.of(...) .sorted() .distinct() 

或者,也许相反:

 Stream.of(...) .distinct() .sorted() 

由于JDK的源代码无法实现这两者的实现,我只是想知道可能的内存消耗和性能影响。

或者,如下所示编写我自己的filter会更高效吗?

 Stream.of(...) .sorted() .filter(noAdjacentDuplicatesFilter()) public static Predicate noAdjacentDuplicatesFilter() { final Object[] previousValue = {new Object()}; return value -> { final boolean takeValue = !Objects.equals(previousValue[0], value); previousValue[0] = value; return takeValue; }; } 

当您在sorted()之后链接distinct()操作时,实现将利用数据的排序特性并避免构建内部HashSet ,这可以通过以下程序演示

 public class DistinctAndSort { static int COMPARE, EQUALS, HASHCODE; static class Tracker implements Comparable { static int SERIAL; int id; Tracker() { id=SERIAL++/2; } public int compareTo(Tracker o) { COMPARE++; return Integer.compare(id, o.id); } public int hashCode() { HASHCODE++; return id; } public boolean equals(Object obj) { EQUALS++; return super.equals(obj); } } public static void main(String[] args) { System.out.println("adjacent sorted() and distinct()"); Stream.generate(Tracker::new).limit(100) .sorted().distinct() .forEachOrdered(o -> {}); System.out.printf("compareTo: %d, EQUALS: %d, HASHCODE: %d%n", COMPARE, EQUALS, HASHCODE); COMPARE=EQUALS=HASHCODE=0; System.out.println("now with intermediate operation"); Stream.generate(Tracker::new).limit(100) .sorted().map(x -> x).distinct() .forEachOrdered(o -> {}); System.out.printf("compareTo: %d, EQUALS: %d, HASHCODE: %d%n", COMPARE, EQUALS, HASHCODE); } } 

这将打印

 adjacent sorted() and distinct() compareTo: 99, EQUALS: 99, HASHCODE: 0 now with intermediate operation compareTo: 99, EQUALS: 100, HASHCODE: 200 

Stream实现无法识别像map(x -> x)这样简单的中间操作,因此,必须假设元素可能不会根据映射函数的结果进行排序。

没有保证会发生这种优化,但是,假设Stream实现的开发人员不会删除该优化甚至尝试添加更多优化是合理的,因此滚动您自己的实现将阻止您的代码从中受益未来的优化。

此外,您创建的是“有状态谓词”,强烈建议不要这样做,当然,在与并行流一起使用时会中断。

如果您不信任Stream API来足够有效地执行此操作,那么在没有Stream API的情况下实现此特定操作可能会更好。

免责声明:我知道性能测试很难,特别是在需要预热的JVM和没有其他进程运行的受控环境中。

如果我测试它我得到这些结果,所以看起来你的实现有利于并行执行。 (在i7上运行4核+超线程)。

所以“ .distinct().sorted() ”似乎比较慢。 正如Holger预测/解释的那样

 Round 1 (Warm up?) 3938 2449 5747 Round 2 2834 2620 3984 Round 3 Parallel 831 4343 6346 Round 4 Parallel 825 3309 6339 

使用代码:

 package test.test; import java.util.Collections; import java.util.List; import java.util.Objects; import java.util.function.Predicate; import java.util.stream.Collectors; import java.util.stream.IntStream; public class SortDistinctTest { public static void main(String[] args) { IntStream range = IntStream.range(0, 6_000_000); List collect = range.boxed().collect(Collectors.toList()); Collections.shuffle(collect); long start = System.currentTimeMillis(); System.out.println("Round 1 (Warm up?)"); collect.stream().sorted().filter(noAdjacentDuplicatesFilter()).collect(Collectors.counting()); long fst = System.currentTimeMillis(); System.out.println(fst - start); collect.stream().sorted().distinct().collect(Collectors.counting()); long snd = System.currentTimeMillis(); System.out.println(snd - fst); collect.stream().distinct().sorted().collect(Collectors.counting()); long end = System.currentTimeMillis(); System.out.println(end - snd); System.out.println("Round 2"); collect.stream().sorted().filter(noAdjacentDuplicatesFilter()).collect(Collectors.counting()); fst = System.currentTimeMillis(); System.out.println(fst - end); collect.stream().sorted().distinct().collect(Collectors.counting()); snd = System.currentTimeMillis(); System.out.println(snd - fst); collect.stream().distinct().sorted().collect(Collectors.counting()); end = System.currentTimeMillis(); System.out.println(end - snd); System.out.println("Round 3 Parallel"); collect.stream().parallel().sorted().filter(noAdjacentDuplicatesFilter()).collect(Collectors.counting()); fst = System.currentTimeMillis(); System.out.println(fst - end); collect.stream().parallel().sorted().distinct().collect(Collectors.counting()); snd = System.currentTimeMillis(); System.out.println(snd - fst); collect.stream().parallel().distinct().sorted().collect(Collectors.counting()); end = System.currentTimeMillis(); System.out.println(end - snd); System.out.println("Round 4 Parallel"); collect.stream().parallel().sorted().filter(noAdjacentDuplicatesFilter()).collect(Collectors.counting()); fst = System.currentTimeMillis(); System.out.println(fst - end); collect.stream().parallel().sorted().distinct().collect(Collectors.counting()); snd = System.currentTimeMillis(); System.out.println(snd - fst); collect.stream().parallel().distinct().sorted().collect(Collectors.counting()); end = System.currentTimeMillis(); System.out.println(end - snd); } public static Predicate noAdjacentDuplicatesFilter() { final Object[] previousValue = { new Object() }; return value -> { final boolean takeValue = !Objects.equals(previousValue[0], value); previousValue[0] = value; return takeValue; }; } }