Java Streams 可以将点列表转换为坐标列表吗?

Can Java Streams transform a list of points into a list of their coordinates?

我在 JavaFX 8 程序中有一个 Point3D 流。为了从它们创建网格,我希望能够生成它们的 (x, y, z) 坐标列表。

通过传统的 Java 循环,这是一项足够简单的任务。 (实际上几乎微不足道。)但是,在未来,我可能会处理数万个点;我非常希望能够使用 Java 流 API 并通过并行流完成此操作。

我想我正在寻找的是这个伪代码的粗略等价物:

List<Double> coordinates = stream.parallel().map(s -> (s.getX(), s.getY(), s.getZ())).collect(Collectors.asList());

到目前为止,我还没有发现这样的功能。有人能给我一个正确的方向吗?

您可以使用 flatMap :

List<Double> coordinates = 
    stream.parallel()
          .flatMap(s -> Stream.of(s.getX(), s.getY(), s.getZ()))
          .collect(Collectors.asList());

为什么?即使使用 "tens of thousands of points",代码也会在很短的时间内完成,您不会真正获得任何东西 "with a parallel stream"。

这听起来像是 premature optimization 的完美示例,其中您可能会使代码复杂化(至少在这种情况下)还不是问题,而且不太可能成为问题。

为了证明我的观点,我创建了下面的测试代码。

为了尽量减少 GC 运行的影响,我 运行 使用 -Xms10g -Xmx10g 这段代码,并添加了显式 gc() 调用,因此测试运行是 运行 "clean slate".

一如既往,性能测试受制于 JIT 优化和其他因素,因此提供了 warm-up 循环。

public static void main(String[] args) {
    Random rnd = new Random();
    List<Point3D> input = new ArrayList<>();
    for (int i = 0; i < 10_000; i++)
        input.add(new Point3D(rnd.nextDouble(), rnd.nextDouble(), rnd.nextDouble()));

    for (int i = 0; i < 100; i++) {
        test1(input);
        test2(input);
    }

    for (int i = 0; i < 10; i++) {
        long start1 = System.nanoTime();
        test1(input);
        long end1 = System.nanoTime();
        System.gc();
        long start2 = System.nanoTime();
        test2(input);
        long end2 = System.nanoTime();
        System.gc();
        System.out.printf("%.6f  %.6f%n", (end1 - start1) / 1_000_000d, (end2 - start2) / 1_000_000d);
    }
}
private static List<Double> test1(List<Point3D> input) {
    List<Double> list = new ArrayList<>();
    for (Point3D point : input) {
        list.add(point.getX());
        list.add(point.getY());
        list.add(point.getZ());
    }
    return list;
}
private static List<Double> test2(List<Point3D> input) {
    return input.stream().parallel()
                         .flatMap(s -> Stream.of(s.getX(), s.getY(), s.getZ()))
                         .collect(Collectors.toList());
}

结果

0.355267  0.392904
0.205576  0.260035
0.193601  0.232378
0.194740  0.290544
0.193601  0.238365
0.243497  0.276286
0.200728  0.243212
0.197022  0.240646
0.192175  0.239790
0.198162  0.279708

虽然并行流似乎稍微慢一些,但没有太大区别。
另请注意,对于 10,000 个点,它在 不到 0.3 毫秒 内完成。
没什么!

让我们尝试将计数从 10,000 增加到 10,000,000(跳过预热):

433.716847  972.100743
260.662700  693.263850
250.699271  736.744653
250.486281  813.615375
249.722716  714.296997
254.704145  796.566859
254.713840  829.755767
253.368331  959.365322
255.016928  973.306254
256.072177  1047.562090

现在并行流的性能明显下降。它慢了 3 倍。这可能是由额外的 GC 运行引起的。

结论:过早的优化是不好的!!!

在你的情况下,你实际上使情况变得更糟。