【问题标题】:java 8 paralel stream reorganize list by given valuejava 8并行流按给定值组织列表
【发布时间】:2016-05-26 12:43:22
【问题描述】:

如果元素小于则向左移动,如果更多则向右移动。 java 7风格的简单代码:

private static <T extends Comparable> List<T> doAction(List<T> original, T state) {
    List<T> left = new ArrayList<T>();
    List<T> fight = new ArrayList<T>();
    for (T e : original) {
        if (e.compareTo(state) < 0) {
            left.add(e);
        } else {
            fight.add(e);
        }
    }
    left.addAll(fight);
    return left;
}

如何使用并行流将上面的代码重写为 java 8 流样式?

【问题讨论】:

  • 您为什么认为并行流会对您的情况有所帮助?你有一个像这样排序的大列表吗?
  • 每个问题都不适合并行化;这可能是其中之一。
  • @Nicolas Filotto:一个有序的并行流将保持遇到的顺序,即使处理顺序不同,参见here
  • @Holger 有趣的谢谢分享

标签: java parallel-processing java-8 java-stream


【解决方案1】:

一种等同于您的原始代码的方法是:

private static <T extends Comparable<T>> List<T> doAction(List<T> original, T state) {
    Map<Boolean, List<T>> collect = original.parallelStream()
        .collect(Collectors.partitioningBy(e -> e.compareTo(state) < 0));
    List<T> left = collect.get(true);
    List<T> right = collect.get(false);
    return Stream.of(left, right).flatMap(List::stream).collect(Collectors.toList());
}

但是,由于您只对数据进行分区,然后再加入它们,一个更简单的解决方案是:

private static <T extends Comparable<T>> List<T> doAction(List<T> original, T state) {
    return original.parallelStream()
        .sorted(Comparator.comparingInt(e -> Integer.signum(e.compareTo(state))|1))
        .collect(Collectors.toList());
}

但请注意,无论哪种情况,您都需要一个相当大的源列表才能从并行执行中受益。


请注意,第二种方法甚至可以就地执行,以防原始 List 是可变的(并且您不再需要原始顺序):

original.sort(Comparator.comparingInt(e -> Integer.signum(e.compareTo(state))|1));

【讨论】:

  • 创建一个包含 2 个列表的新 Stream 来构建一个列表真的有帮助吗?为什么不简单地 left.addAll(right);return left;?
  • @Nicolas Filotto:因为 left 不能保证是一个可变列表。在当前的实现中,它是一个ArrayList,但这可能会改变。您必须使用更复杂的收集器Collectors.partitioningBy(…, Collectors.toCollection(ArrayList::new)) 以确保您可以在结果列表中调用addAll。但是,除非您有一个非常小的right 列表,否则left 的容量不太可能足以满足随后的addAll,因此在后台执行的复制操作的数量不会改变。所以我让收集器保持简单。
  • 或者,你可以说left=new ArrayList&lt;&gt;(left); left.addAll(right);,或者,如果性能很重要,result=new ArrayList&lt;&gt;(left.size()+right.size()); result.addAll(left); result.addAll(right);,如上所述,我只是想保持简单。无论如何,我更喜欢第二种变体。
  • 是的,第二种方法确实更好,因为我们知道结果列表的大小,所以它会更快,我知道稍后我们可以获得一个不可变列表,但我的意思只是避免过度使用 Streams即使对于像附加 2 个列表这样的琐碎操作。
  • “如果你只有一把锤子,那么一切看起来都像钉子”:-)
【解决方案2】:

虽然 Holger 的回答很棒,但您(或其他读者)可能有兴趣编写自己的 Collector 或想知道您将如何编写。

首先,您需要一种表示部分结果的方法

static class PartialResult<T extends Comparable<T>>  {
    public List<T> left = new ArrayList<>();
    public List<T> right = new ArrayList<>();
}

现在我们定义自己的Collector

  static class PartitionCollector<T extends Comparable<T>> implements Collector<T,PartialResult<T>,List<T>>{

    private final T pivot;

    public PartitionCollector(T pivot){
      this.pivot = pivot;
    }

    @Override
    public Supplier<ResultPair<T>> supplier() {
        return ResultPair::new;
    }

    @Override
    public BiConsumer<PartialResult<T>, T> accumulator() {
        return  (result, e) -> {
        if (e.compareTo(pivot) < 0) {
              result.left.add(e);
          }else {
              result.right.add(e);
          }
        };
    }

    @Override
    public BinaryOperator<PartialResult<T>> combiner() {
        BinaryOperator<PartialResult<T>> mergeOp = (r1,r2) ->{
           r1.left.addAll(r2.left);
           r1.right.addAll(r2.right);
           return r1;
        };
        return mergeOp;
    }

    @Override
    public Function<PartialResult<T>, List<T>> finisher() {
        Function<PartialResult<T>,List<T>> finisher = r -> {
            List<T> finalResult = new ArrayList<>(r.left.size() + r.right.size());
            finalResult.addAll(r.left);
            finalResult.addAll(r.right);
            return finalResult;
        };
        return finisher;
    }

    @Override
    public Set<Characteristics> characteristics() {
        return Collections.unmodifiableSet(EnumSet.of(Characteristics.CONCURRENT));
    }
}
  1. supplier 很简单,我们只需要告诉它创建一个新实例。
  2. accumulator,给定一个元素,我们将其放入相应的部分。
  3. combiner,我们有两个部分结果(多线程计算了我们解决方案树的两个分支),将它们合并为一个。
  4. finsiher,最后,我想要一个完整的列表,而不是部分解决方案。

【讨论】:

  • 请注意,组合器可以修改其参数之一并将其返回,因此您可以简单地执行(r1,r2) -&gt; { r1.left.addAll(r2.left); r1.right.addAll(r2.right); return r1; },而不是创建新的ResultPair。此外,累加器可以简化为(result, e) -&gt; (e.compareTo(pivot)&lt;0? result.left: result.right).add(e)
猜你喜欢
  • 1970-01-01
  • 2021-09-11
  • 1970-01-01
  • 2019-08-01
  • 1970-01-01
  • 1970-01-01
  • 2015-08-06
  • 2019-12-26
  • 1970-01-01
相关资源
最近更新 更多