Arrays.asList和Arrays.stream使用forEach()的区别

17
如果您有一个数组并且想要使用Java8的forEach()方法,哪种方法更好或更有效呢?
Arrays.asList(new String[]{"hallo","hi"}).forEach(System.out::println);

或者

Arrays.stream(new String[]{"hallo","hi"}).forEach(System.out::println);

这个差异是否显著,或者有没有更好的解决方案来解决这个问题?


1
可能是asList,它涉及的对象较少。但在程序执行中仍然不会注意到任何差异。 - Sotirios Delimanolis
3
如果您还没有数组,而想要创建 "hello""hi" 两个元素的列表,可以使用 Arrays.asList("hello","hi") 方法来实现。这样做不需要先创建一个数组。 - Dici
3个回答

20

不需要。如果您已经有了一个数组,

String[] array;

我会使用:

Arrays.stream(array).forEach(System.out::println);

因为您将数组转换为流的工作交给了JDK - 让它负责效率等问题。

但是,既然您没有一个数组,我会使用Stream.of()的可变参数来创建一个值的流:

Stream.of("hallo","hi").forEach(System.out::println);

这再次让JDK负责以其认为合适的方式有效地流式传输值。


我不明白你的“如果你已经有一个数组”的意思。无论如何,它们都需要一个数组。“stream”需要一个数组,“asList”也是如此。 - Sotirios Delimanolis
4
“@SotiriosDelimanolis 'already has an array' 的意思是指 '不在代码中直接编写数组元素' - 也就是说,该数组已经在代码之外被声明和填充了值。” - Bohemian
@SotiriosDelimanolis 是的,易读性(始终高居设计雷达之上,我相信“少代码就是好”的理念)性能 - 它将更多的操作交给JDK(我们倾向于相信它是“写得很好”的)。 - Bohemian
3
为了让决定变得更加困难,我会提供这样一种方式:如果你没有现成的数组,你也可以使用 Stream.of("hallo","hi").forEach(System.out::println);。在长度为 1 的特殊情况下,这将更有效率,因为不需要创建数组。 - Holger

14

看起来几乎没有任何区别。我为此创建了一个测试类。在五次运行的过程中,我的输出如下:

Run 1:
Arrays.asList() method................: 3231 ms
Arrays.stream() method................: 3111 ms
Stream.of() method....................: 3031 ms
Arrays.asList() (premade array) method: 3086 ms
Arrays.stream() (premade array) method: 3231 ms
Stream.of() (premade array) method....: 3191 ms

Run 2:
Arrays.asList() method................: 3270 ms
Arrays.stream() method................: 3072 ms
Stream.of() method....................: 3086 ms
Arrays.asList() (premade array) method: 3002 ms
Arrays.stream() (premade array) method: 3251 ms
Stream.of() (premade array) method....: 3271 ms

Run 3:
Arrays.asList() method................: 3307 ms
Arrays.stream() method................: 3092 ms
Stream.of() method....................: 2911 ms
Arrays.asList() (premade array) method: 3035 ms
Arrays.stream() (premade array) method: 3241 ms
Stream.of() (premade array) method....: 3241 ms

Run 4:
Arrays.asList() method................: 3630 ms
Arrays.stream() method................: 2981 ms
Stream.of() method....................: 2821 ms
Arrays.asList() (premade array) method: 3058 ms
Arrays.stream() (premade array) method: 3221 ms
Stream.of() (premade array) method....: 3214 ms

Run 5:
Arrays.asList() method................: 3338 ms
Arrays.stream() method................: 3174 ms
Stream.of() method....................: 3262 ms
Arrays.asList() (premade array) method: 3064 ms
Arrays.stream() (premade array) method: 3269 ms
Stream.of() (premade array) method....: 3275 ms

从输出结果来看,Stream.of() 方法似乎微不足道地(但一致地)最有效率,而

Stream.of("hallo","hi").forEach(System.out::println);

代码很易读。Stream.of的优点在于它不需要将数组转换为列表,也不需要创建数组然后创建流,而是可以直接从元素中创建流。对我来说有些惊讶的是,由于我的测试方式,每次使用Stream.of()实例化一个新数组流要比传入预先制作的数组更快,可能是因为引用外部变量的“捕获”Lambda函数的效率稍低。

以下是我的测试类代码:

import java.util.Arrays;
import java.util.function.Consumer;
import java.util.stream.Stream;


public class StreamArrayTest {

    public static void main(String[] args){
        System.out.println("Arrays.asList() method................: " + arraysAsListMethod() + " ms");
        System.out.println("Arrays.stream() method................: " + arraysStreamMethod() + " ms");
        System.out.println("Stream.of() method....................: " + streamOfMethod() + " ms");
        System.out.println("Arrays.asList() (premade array) method: " + presetArraysAsListMethod() + " ms");
        System.out.println("Arrays.stream() (premade array) method: " + presetArraysStreamMethod() + " ms");
        System.out.println("Stream.of() (premade array) method....: " + presetStreamsOfMethod() + " ms");
    }

    private static Long timeOneMillion(Runnable runner){
        MilliTimer mt = MilliTimer.start();
        for (int i = 0; i < 1000000; i++){
            runner.run();
        }
        return mt.end();
    }

    private static Long timeOneMillion(String[] strings, Consumer<String[]> consumer){
        MilliTimer mt = MilliTimer.start();
        for (int i = 0; i < 1000000; i++){
            consumer.accept(strings);
        }
        return mt.end();        
    }

    public static Long arraysAsListMethod(){
        return timeOneMillion(()->Arrays.asList(new String[]{"hallo","hi","test","test2","test3","test4","test5","test6","test7","test8"}).forEach(StreamArrayTest::doSomething));
    }

    public static Long arraysStreamMethod(){
        return timeOneMillion(()->Arrays.stream(new String[]{"hallo","hi","test","test2","test3","test4","test5","test6","test7","test8"}).forEach(StreamArrayTest::doSomething));
    }

    public static Long streamOfMethod(){
        return timeOneMillion(()->Stream.of("hallo","hi","test","test2","test3","test4","test5","test6","test7","test8").forEach(StreamArrayTest::doSomething));        
    }   

    public static Long presetArraysAsListMethod(){
        String[] strings = new String[]{"hallo","hi","test","test2","test3","test4","test5","test6","test7","test8"};
        return timeOneMillion(strings, (s)->Arrays.asList(s).forEach(StreamArrayTest::doSomething));    
    }

    public static Long presetArraysStreamMethod(){
        String[] strings = new String[]{"hallo","hi","test","test2","test3","test4","test5","test6","test7","test8"};
        return timeOneMillion(strings, (s)->Arrays.stream(s).forEach(StreamArrayTest::doSomething));    
    }

    public static Long presetStreamsOfMethod(){
        String[] strings = new String[]{"hallo","hi","test","test2","test3","test4","test5","test6","test7","test8"};
        return timeOneMillion(strings, (s)->Stream.of(s).forEach(StreamArrayTest::doSomething));    
    }

    public static void doSomething(String s){
        String result = s;
        for (int i = 0; i < 10; i++){
            result = result.concat(s);
        }
    }
}

我使用的MilliTimer类:

public class MilliTimer {
    private long startTime = 0L;

    private MilliTimer(long startTime){
        this.startTime = startTime;
    }

    public static MilliTimer start(){
        return new MilliTimer(System.currentTimeMillis());
    }

    public long end() throws IllegalArgumentException {
        return System.currentTimeMillis() - startTime;
    }
}

优化器更喜欢使用局部变量。因此,使用临时数组的代码可能确实比使用预准备数组的代码快,甚至更快,但是不清楚这是否适用于您的基准测试,因为它有缺陷。您的方法doSomething可能看起来很复杂,但实际上没有任何作用,因为其中的所有内容都没有真正的效果。现代JVM能够识别到这一点。除此之外,您应该使用System.nanoTime()而不是System.currentTimeMillis(),因为后者返回的值不能保证持续向前移动。 - Holger
doSomething方法确实会做一些事情... 它会实例化许多字符串对象并将它们连接在一起。如果你不相信它会这么做,那就把那四行代码删掉,看看执行时间从大约3200毫秒变成了大约30毫秒。 - Steve K
如果您调整doSomething方法内部循环的迭代次数,那么这也会影响执行时间。 - Steve K

1
Arrays.asList() method................: 22 ms
Arrays.stream() method................: 26 ms
Stream.of() method....................: 26 ms
Arrays.asList() (premade array) method: 8 ms
Arrays.stream() (premade array) method: 30 ms
Stream.of() (premade array) method....: 17 ms

当你将doSomething更改为实际上什么也不做,如下所示:
public static void doSomething(String s){
}

那么,您正在测量这些操作的实际速度,而不是操作String = String + String; 这就是doSomething正在做的事情,当然,它的速度一直大致相同。然而,实际速度并不相同,使用预制数组的asList要快得多。

其他人已经注意到了真正的结果,即应该注意流(stream),因为它通常比普通的Java(非lambda)方法慢4倍。


1
如果您删除doSomething的主体,则没有考虑各种处理方法的输入/输出。是的,可以使用Arrays.asList()更快地实例化列表,而不是Stream.of()Arrays.stream(),但是如果您实际上不对数组中的值执行任何操作,则没有处理开销,因为优化器将识别noop方法并将其剪切掉。 doSomething()的时间应该是一致的,因为它们是完全相同的操作,剩下的大部分应该是调用各种处理方法的开销。 - Steve K

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接