2017-02-02 50 views
0

考慮下面的代碼:Collection.toArray()VS Collection.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); 

結果是使用流大約慢了四倍。在我的機器上,816ms(流)vs 187ms(無流)。我也嘗試切換周圍的時間語句(myArray1之前的myArray2),這對結果影響不大。爲什麼這麼慢?創建一個Stream如此計算密集?

我跟着@霍爾格的意見,並研究了JVM上測試了一下(當然還不夠),閱讀this postthis articlethis article,並使用JMH


結果(經由JMH):

​​

StreamToArrayArrayListBenchmark.testMethod avgt 5 2846.346 32.500±納秒/運算

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納秒/運算


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納秒/運算

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納秒/運算

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納秒/運算


總結:

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

使用江鈴控股有限公司(可能天真地),我仍然看到ArrayList::toArray大約快兩倍Stream::toArray。然而,這似乎是因爲ArrayList只是做陣列拷貝的能力,就像@Andreas指出的那樣,因爲當源是LinkedList時,結果大致相等。

瞭解myList.toArray(new String[0])絕對很好。

+0

如果使用Arrays.stream(),該怎麼辦? –

+0

@JoshLee在我的真實代碼中,我有一個List,我試圖將其轉換爲數組。我不是如何使用Arrays.stream()? – dantiston

+0

@Holger你是否認爲這是重複的,我的結論是錯誤的?我並沒有真的在詢問如何做基準 - 我假設我的基準不是100%準確的。我真的在問'流'的開銷 - 這在另一個問題中沒有涉及。 – dantiston

回答

2

Arrays.asList()創建一個固定大小的List,該參數直接由可變參數數組參數指定。 Javadoc甚至這樣說:

返回由指定數組支持的固定大小列表。

其執行toArray()是一個簡單的System.arraycopy()非常快

在另一方面,當你做myList.stream().toArray(String[]::new),大小是不知道的,所以Stream.toArray()方法具有消費流,收集所有的值,然後創建陣列和值複製到陣列。這是很多較慢,並且需要很多更多內存

總之,這是浪費資源。

如果你想更簡單,只是不給數組的大小。與使用Streams相比,它仍然更快,內存更密集:

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

我沒有'知道你可以使用toArray和一個空數組。謝謝你的提示!給它一個預先大小的陣列似乎幾乎同等速度。 – dantiston

+0

我剛剛嘗試過使用LinkedList而不是Arrays.asList(),行爲相同,LinkedList與ArrayList的時間幾乎完全相同。似乎所有的開銷都在流中。 – dantiston

+0

無論你如何分片(可以這麼說),數組副本只是對JVM的一次調用,而任何庫解決方案都將至少有一個顯式循環。 –

0

在引擎蓋下,流比普通數組複雜得多。編譯器會變得更好,但是目前,順序for循環應該比流操作快。

This article有一些關於流管道的背景知識,用於實現流。它可以幫助理解它背後的複雜性。

流的優點是代碼可以更清晰並且更容易並行化。