Collection.toArray()vs Collection.stream()。toArray()

时间:2017-02-02 21:19:06

标签: java collections java-stream

请考虑以下代码:

List<String> myList = Arrays.asList(1, 2, 3);
String[] myArray1 = myList.toArray(new String[myList.size()]);
String[] myArray2 = myList.stream().toArray(String[]::new);
assert Arrays.equals(myArray1, myArray2);

在我看来,使用流更简单。

因此,我测试了每个的速度。

List<String> myList = Arrays.asList("1", "2", "3");
double start;

start = System.currentTimeMillis();
for (int i = 0; i < 10_000_000; i++) {
    String[] myArray1 = myList.toArray(new String[myList.size()]);
    assert myArray1.length == 3;
}
System.out.println(System.currentTimeMillis() - start);

start = System.currentTimeMillis();
for (int i = 0; i < 10_000_000; i++) {
    String[] myArray2 = myList.stream().toArray(String[]::new);
    assert myArray2.length == 3;
}
System.out.println(System.currentTimeMillis() - start);

结果是使用流的速度大约慢了四倍。在我的机器上,816毫秒(流)与187毫秒(没有流)。我也尝试切换时序语句(myArray1之前的myArray2),这对结果影响不大。为什么这么慢?创建一个Stream计算密集型?

我遵循了@ Holger的建议并对JVM测试进行了一些研究(当然不够),阅读this postthis articlethis article和使用JMH。< / p>

结果(通过JMH):

private static final List<String> myList = IntStream.range(1, 1000).mapToObj(String::valueOf).collect(Collectors.toList());

@Benchmark
public void testMethod() {
    String[] myArray = myArrayList.stream().toArray(String[]::new);
}
  

StreamToArrayArrayListBenchmark.testMethod avgt 5 2846.346±32.500 ns / op

private static final List<String> myList = IntStream.range(1, 1000).mapToObj(String::valueOf).collect(Collectors.toList());

@Benchmark
public void testMethod() {
    String[] myArray = myArrayList.toArray(new String[0]);
}
  

ToArrayEmptyArrayListBenchmark.testMethod avgt 5 1417.474±20.725 ns / op

private static final List<String> myList = IntStream.range(1, 1000).mapToObj(String::valueOf).collect(Collectors.toList());

@Benchmark
public void testMethod() {
    String[] myArray = myArrayList.toArray(new String[myList.size()]);
}
  

ToArraySizedArrayListBenchmark.testMethod avgt 5 1853.622±178.351 ns / op

private static final List<String> myList = new LinkedList<>(IntStream.range(1, 1000).mapToObj(String::valueOf).collect(Collectors.toList()));

@Benchmark
public void testMethod() {
    String[] myArray = myArrayList.stream().toArray(String[]::new);
}
  

StreamToArrayLinkedListBenchmark.testMethod avgt 5 4152.003±59.281 ns / op

private static final List<String> myList = new LinkedList<>(IntStream.range(1, 1000).mapToObj(String::valueOf).collect(Collectors.toList()));

@Benchmark
public void testMethod() {
    String[] myArray = myArrayList.toArray(new String[0]);
}
  

ToArrayEmptyLinkedListBenchmark.testMethod avgt 5 4089.550±29.880 ns / op

private static final List<String> myList = new LinkedList<>(IntStream.range(1, 1000).mapToObj(String::valueOf).collect(Collectors.toList()));

@Benchmark
public void testMethod() {
    String[] myArray = myArrayList.toArray(new String[myList.size()]);
}
  

ToArraySizedArrayListBenchmark.testMethod avgt 5 4115.557±93.964 ns / op

总结:

              | ArrayList | LinkedList
stream        | 2846      | 4152
toArray sized | 1853      | 4115
toArray empty | 1417      | 4089

使用JMH(可能天真地),我仍然看到ArrayList::toArray的速度是Stream::toArray的两倍。但是,这似乎是因为ArrayList能够执行数组副本,正如@Andreas指出的那样,因为当源是LinkedList时,结果大致相等。

了解myList.toArray(new String[0])非常好。

2 个答案:

答案 0 :(得分:6)

Arrays.asList()创建一个固定大小的List,由varargs数组参数直接支持。 Javadoc甚至这么说:

  

返回由指定数组支持的固定大小的列表。

toArray()的实施很简单System.arraycopy()非常快

另一方面,当您执行myList.stream().toArray(String[]::new)时,大小未知,因此Stream.toArray()方法必须使用流,收集所有值,然后创建数组并将值复制到数组中。这是,需要很多更多内存

简而言之,这是浪费资源。

如果您想要更简单,请不要给出数组大小。它比使用Streams更快,内存更少:

String[] myArray1 = myList.toArray(new String[0]);

答案 1 :(得分:0)

在引擎盖下,流比普通阵列复杂得多。编译器会变得更好,但是目前,循环顺序应该比流操作更快。

This article有一些关于流管道的背景知识,用于实现流。它可以帮助理解它背后的复杂性。

流的优点是代码可以更清晰,并且更容易并行化。