【发布时间】:2017-02-02 21:19:05
【问题描述】:
考虑以下代码:
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 毫秒(无流)。我还尝试切换时间语句(myArray2 在 myArray1 之前),这对结果影响不大。为什么这么慢?创建Stream 的计算量这么大吗?
我听从了@Holger 的建议,对 JVM 测试进行了一些研究(当然还不够),阅读了this post、this article、this article,并使用了JMH。
结果(通过 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])。
【问题讨论】:
-
如果你使用
Arrays.stream()会怎样? -
@JoshLee 在我的真实代码中,我有一个要转换为数组的列表。我不是我会如何使用 Arrays.stream() 吗?
-
@Holger 你是说把它标记为重复我的结论是错误的吗?我并不是真的在问如何进行基准测试——我假设我的基准测试不是 100% 准确的。我真的在问
Stream的开销——另一个问题没有涉及。 -
我们不是在谈论“100% 准确”,您违反了那里提到的每条 规则。当然,从这种根本上被打破的基准得出的结论是错误的。对于现实生活中的用例,这些方法之间的实际差异接近于零。
-
@Holger 好的——这是对我的问题的合理回答。我仍然不明白这是如何重复的。如果你的答案是“你的基准测试是错误的——这些是等价的”,为什么不在答案中这么说呢?
标签: java collections java-stream