是否有效地收集了方法



我最近开始使用收集和收集,发现它与其他编码过程相对长时间,我用于执行类似的任务。

这是我的代码:

        System.out.println("CollectingAndThen");
        Long t = System.currentTimeMillis();
        String personWithMaxAge = persons.stream()
                                        .collect(Collectors.collectingAndThen(
                                                                Collectors.maxBy(Comparator.comparing(Person::getAge)),
                                                                (Optional<Person> p) -> p.isPresent() ? p.get().getName() : "none"
                                                ));

        System.out.println("personWithMaxAge - "+personWithMaxAge + " time taken = "+(System.currentTimeMillis() - t));
        Long t2 = System.currentTimeMillis();
        String personWithMaxAge2 = persons.stream().sorted(Comparator.comparing(Person::getAge).reversed())
                                                    .findFirst().get().name;
        System.out.println("personWithMaxAge2 : "+personWithMaxAge2+ " time taken = "+(System.currentTimeMillis() - t2));

在这里输出:

CollectingAndThen
personWithMaxAge - Peter time taken = 17
personWithMaxAge2 : Peter time taken = 1

表明收集和比较花费更大的时间。

所以我的问题是 - 我应该继续收集和其他建议吗?

collectingAndThen添加了一个刚刚在集合末尾执行的动作。

so

String personWithMaxAge = persons.stream()
    .collect(Collectors.collectingAndThen(
        Collectors.maxBy(Comparator.comparing(Person::getAge)),
        (Optional<Person> p) -> p.isPresent() ? p.get().getName() : "none"
    ));

没有什么不同
Optional<Person> p = persons.stream()
    .collect(Collectors.maxBy(Comparator.comparing(Person::getAge)));
String personWithMaxAge = p.isPresent() ? p.get().getName() : "none";

在收集器中指定操作的实际优势显示,当您使用结果收集器作为另一个收集的输入时,例如groupingBy(f1, collectingAndThen(collector, f2))

由于这是一次琐碎的单个动作,因此对性能没有影响。此外,基于sorted的解决方案不太可能比任何非平地输入的maxBy操作都快。

您只是在使用违反"我如何在Java中编写正确的微基准?"中列出的几个规则的基准测试。最值得注意的是,您正在测量流框架第一次使用的初始初始化开销。只需交换两个操作的顺序就会给您带来完全不同的结果。

仍然,无需使操作不必要地复杂。如前所述,收藏家反映现有流动运营的优势在于它们可以与其他收藏家结合使用。如果不需要这样的组合,只需使用直接的代码

String personWithMaxAge = persons.stream()
    .max(Comparator.comparing(Person::getAge))
    .map(Person::getName).orElse("none");

比收集器使用更简单,比基于sorted的解决方案更有效。

tl; dr;您测量事物的方式可能已经关闭。

我使用JMH创建了一个更有效的性能台,设置为以下(人员列表的初始初始化,以进一步增强结果的信心):

public static void main(String[] args) throws RunnerException {
    Options opt = new OptionsBuilder().include(SO.class.getSimpleName()).jvmArgs("-Dfile.encoding=UTF-8").build();
    new Runner(opt).run();
}
public static final class Person {
    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
    String name;
    int age;
    public int getAge() {return age;};
    public String getName() {return this.name;}
}
public static final List<Person> persons = IntStream.range(0, 100).mapToObj(i -> new Person("person" + i, RandomUtils.nextInt(0, 50))).collect(Collectors.toList());
@Benchmark
@BenchmarkMode(Mode.Throughput)
@OutputTimeUnit(TimeUnit.SECONDS)
@Warmup(iterations = 5, time = 4, timeUnit = TimeUnit.SECONDS)
@Measurement(iterations = 4, time = 5, timeUnit = TimeUnit.SECONDS)
@Fork(2)
public float collectingAndThen() {
    String personWithMaxAge = persons.stream()
            .collect(Collectors.collectingAndThen(
                    Collectors.maxBy(Comparator.comparing(Person::getAge)),
                    (Optional<Person> p) -> p.isPresent() ? p.get().getName() : "none"
            ));
    return personWithMaxAge.length() * 1.0f;
}
@Benchmark
@BenchmarkMode(Mode.Throughput)
@OutputTimeUnit(TimeUnit.SECONDS)
@Warmup(iterations = 5, time = 4, timeUnit = TimeUnit.SECONDS)
@Measurement(iterations = 4, time = 5, timeUnit = TimeUnit.SECONDS)
@Fork(2)
public float sortFindFirst() {
    String personWithMaxAge2 = persons.stream().sorted(Comparator.comparing(Person::getAge).reversed())
            .findFirst().get().name;
    return personWithMaxAge2.length() * 1.0f;
}

结果对此测试毫无疑问(列表中有100人):

# Run complete. Total time: 00:02:41
Benchmark                        Mode  Cnt        Score       Error  Units
Benchmark.SO.collectingAndThen  thrpt    8  1412304,072 ± 53963,266  ops/s
Benchmark.SO.sortFindFirst      thrpt    8   331214,270 ±  7966,082  ops/s

收集和收集速度快4倍。

如果将人列表缩小到5个人,这些数字会完全改变:

Benchmark                        Mode  Cnt         Score        Error  Units
Benchmark.SO.collectingAndThen  thrpt    8  14529905,529 ± 423196,066  ops/s
Benchmark.SO.sortFindFirst      thrpt    8   7645716,643 ± 538730,614  ops/s

但是收集和收集速度仍然快2倍。

我怀疑您的测试是否已关闭。有很多可能的原因,例如,classloading,jit汇编和其他热身,...

正如@Assylias在评论中指出的那样,您应该依靠精心制作的微型基准来测量这种"小"方法的时间,以避免先前所述的副作用。请参阅:如何在Java中编写正确的微基准?

no,从效率角度来看, collectingAndThen很好。

考虑此代码用于生成随机整数列表:

List<Integer> list =
    new Random().ints(5).boxed().collect(Collectors.toList());

您可以使用两种方法从此列表中获得最大值:

    list.stream().collect(Collectors.collectingAndThen(
        Collectors.maxBy(Comparator.naturalOrder()),
        (Optional<Integer> n) -> n.orElse(0)));

    list.stream().sorted().findFirst().get();

如果您只是时间执行这两种方法,则可能会得到这样的时间(iDeone):

collectAndThen 2.884806
sortFindFirst  1.898522

那是毫秒毫秒的时代。

但要继续迭代,您会发现时间发生了巨大变化。100次迭代后:

collectAndThen 0.00792
sortFindFirst  0.010873

静止时段。

因此,如前所述,您只是不正确地对这两种方法进行基准测试。

相关内容

最新更新