java8流是否会产生慢速代码?

eng*_*yin 2 performance functional-programming java-8 java-stream

关于函数式编程,特别是新的Java 8流API,有太多的炒作.它被宣传为旧的良好循环和命令式范例的良好替代品.事实上,有时它看起来很不错并且做得很好.但性能呢?

例如,这是关于此的好文章:Java 8:没有更多循环 使用循环,您可以通过一次迭代完成所有工作.但是使用新的流API,您将链接多个循环,这使得它更慢(是不是?).看看他们的第一个样本.在大多数情况下,Loop甚至不会遍历整个阵列.但是,要使用新的流API进行过滤,您必须遍历整个数组以过滤掉所有候选项,然后您将能够获得第一个.

在这篇文章中提到了一些懒惰:

我们首先使用filter操作来查找具有Java标记的所有文章,然后使用findFirst()操作来获取第一个匹配项.由于流是惰性的并且过滤器返回流,因此该方法仅处理元素,直到找到第一个匹配.

作者对这种懒惰的意思是什么?

我做了简单的测试,它表明旧的良好循环解决方案可以快速运行10倍然后流式处理.

public void test() {
    List<String> list = Arrays.asList(
            "First string",
            "Second string",
            "Third string",
            "Good string",
            "Another",
            "Best",
            "Super string",
            "Light",
            "Better",
            "For string",
            "Not string",
            "Great",
            "Super change",
            "Very nice",
            "Super cool",
            "Nice",
            "Very good",
            "Not yet string",
            "Let's do the string",
            "First string",
            "Low string",
            "Big bunny",
            "Superstar",
            "Last");

    long start = System.currentTimeMillis();
    for (int i = 0; i < 100000000; i++) {
        getFirstByLoop(list);
    }
    long end = System.currentTimeMillis();

    System.out.println("Loop: " + (end - start));

    start = System.currentTimeMillis();
    for (int i = 0; i < 100000000; i++) {
        getFirstByStream(list);
    }
    end = System.currentTimeMillis();

    System.out.println("Stream: " + (end - start));
}

public String getFirstByLoop(List<String> list) {

    for (String s : list) {
        if (s.endsWith("string")) {
            return s;
        }
    }

    return null;
}

public Optional<String> getFirstByStream(List<String> list) {
    return list.stream().filter(s -> s.endsWith("string")).findFirst();
}
Run Code Online (Sandbox Code Playgroud)

结果是:

循环:517

流:5790

顺便说一句,如果我将使用String []而不是List,差异会更大!差不多100倍!

问题:如果我正在寻找最佳的代码性能,我应该使用旧的循环命令方法吗?FP范式只是为了使代码"更简洁和可读"而不是性能吗?

要么

有什么我错过了新的流API可能至少与循环命令式方法一样有效吗?

Eug*_*ene 8

懒惰是关于如何从流的源中获取元素 - 这是按需的.如果需要采取更多的元素 - 他们会,否则他们不会.这是一个例子:

 Arrays.asList(1, 2, 3, 4, 5)
            .stream()
            .peek(x -> System.out.println("before filter : " + x))
            .filter(x -> x > 2)
            .peek(System.out::println)
            .anyMatch(x -> x > 3);
Run Code Online (Sandbox Code Playgroud)

注意每个元素如何遍历整个阶段的管道; 那是filter应用于一个元素 - 不是所有元素,因此filter返回一个Stream<Integer>.这允许流短路,因为anyMatch根本不需要处理,5因为根本不需要.

请注意,并非所有中间操作都是懒惰的.例如sorted,distinct不是 - 这些被称为有状态中间操作.想想这种方式-实际上你排序元素需要遍历整个源.另一个不直观的例子是flatMap,但这不能保证,看起来更像是一个错误,更多内容在这里阅读

速度是关于你如何衡量,测量java中的微基准并不容易,事实上的工具就是jmh- 你可以尝试一下.SO上有很多帖子表明流确实比较慢(这在正常情况下 - 它们有基础设施),但差别并不大到实际关心.


Ste*_*n C 6

问题:如果我正在寻找最佳的代码性能,我应该使用旧的循环命令方法吗?

现在,可能是的.各种基准测试似乎表明,对于大多数测试,流比循环慢.虽然不是灾难性的慢.

反例:

可以用循环做同等的事情,你不能只用循环来做.

但最重要的是,性能很复杂,而且流还不是加速代码的神奇子弹.

FP范式只是为了使代码"更简洁和可读"而不是性能吗?

不完全是.毫无疑问,FP范式更简洁,并且(对熟悉它的人)更具可读性.

但是,通过表达使用FP范例,您可以通过使用循环和赋值表达的代码更难以实现的方式来表达它.FP代码也更适合正式方法; 即正确的正式证明.