Collectors.toSet 实现细节

Collectors.toSet implementation detail

我正在查看 jdk-8 下的 Collectors.toSet 实现,几乎看到了显而易见的事情:

 public static <T> Collector<T, ?, Set<T>> toSet() {
    return new CollectorImpl<>(
       (Supplier<Set<T>>) HashSet::new, 
       Set::add,
       (left, right) -> { left.addAll(right); return left; }, // combiner
       CH_UNORDERED_ID);

看一下combiner;这在 之前已经讨论过,但想法是 a combiner folds from the second argument into the first。这显然发生在这里。

但后来我研究了 jdk-9 实现并看到了这个:

 public static <T> Collector<T, ?, Set<T>> toSet() {
    return new CollectorImpl<>(
       (Supplier<Set<T>>) HashSet::new, 
       Set::add,
       (left, right) -> {
          if (left.size() < right.size()) {
            right.addAll(left); return right;
          } else {
             left.addAll(right); return left;
          }
       },
       CH_UNORDERED_ID);

现在 为什么 发生这种情况有点明显 - 添加 less elements to a bigger Set, then the other way around 花费的时间更少。但这真的比普通 addAll 便宜吗,还要考虑分支的额外开销吗?

这也违反了我关于总是向左折叠的定律...

有人可以在这里阐明一下吗?

一个Collector的combiner函数将接收 leftright适当的,如果有一个相遇顺序要维护,然而,这取决于 Collector,它将如何实际结合这两个参数。

documentation 状态:

A function that accepts two partial results and merges them. The combiner function may fold state from one argument into the other and return that, or may return a new result container.

为了收集成一个List,如果我们只是把left.addAll(right)换成right.addAll(left),那将是灾难性的,但是对于一个无序的Set,这无关紧要. toSet() 收集器甚至报告 UNORDERED 特征以提示 Stream(或任何客户端代码)它甚至不关心提供哪个参数作为 leftright,因此并行流可以组合任意部分结果,无论先完成的是什么,换句话说,它可能表现得像无序流,即使源有遇到顺序(Java 8 的实现没有'不要利用那个机会)。

关于它是否值得......我们正在比较一个额外的分支与我们可以节省的可能数千个 add 操作,每个 轴承 多个内部条件分支…