2017-02-02 11 views
0

Рассмотрим следующий код:Collection.toArray() против Collection.stream() ToArray()

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 столь интенсивным с точки зрения вычислительной мощности?

Я последовал совету @ Хольгер и изучал немного (конечно, не достаточно) на тестировании 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 нс/оп

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 нс/оп

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 

Использование JMH (возможно, по наивности), я по-прежнему видим, что ArrayList::toArray примерно в два раза быстрее, чем Stream::toArray.Однако, похоже, это связано с тем, что ArrayList просто выполняет копию массива, поскольку @Andreas указал, потому что, когда источником является LinkedList, результаты примерно равны.

Это определенно полезно знать о myList.toArray(new String[0]).

+0

Что делать, если вы используете 'Arrays.stream()'? –

+0

@JoshLee в моем реальном коде, у меня есть список, который я пытаюсь преобразовать в массив. Я не так, как с ним использовать Arrays.stream()? – dantiston

+0

@ Хольджер вы говорите, отмечая это как дубликат, что мой вывод неправильный? Я не спрашиваю о том, как делать бенчмаркинг - я полагаю, что мои тесты не на 100% точны. Я действительно спрашиваю о накладных расходах «Стрим» - это не рассматривается в другом вопросе. – dantiston

ответ

2

Arrays.asList() создает фиксированный размер List, который напрямую поддерживается параметром массива varargs. Javadoc даже так говорит:

Возвращает список фиксированного размера, поддерживаемый указанным массивом.

Его реализация toArray() - это простой System.arraycopy(). Очень быстро.

С другой стороны, когда вы делаете myList.stream().toArray(String[]::new), размер не известен, поэтому метод Stream.toArray() должен потреблять поток, собрать все ценности, затем создать массив и скопировать значения в массив. Это много slower, и требует много больше памяти.

Одним словом, это пустая трата ресурсов.

Если вы хотите проще, просто не указывайте размер массива. Это все еще намного быстрее и меньше памяти, чем использование потоков:

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

Я didn ' t знать, что вы можете использовать toArray с пустым массивом. Спасибо за совет! Кажется почти равной скорости, чтобы дать ему массив до размера. – dantiston

+0

Я просто пробовал это с помощью LinkedList вместо Arrays.asList(), и поведение такое же, и времена для LinkedList vs ArrayList почти идентичны. Похоже, все накладные расходы находятся в Stream. – dantiston

+0

Независимо от того, как вы его обрезаете (так сказать), копия массива - это всего лишь один вызов JVM, тогда как любое решение библиотеки будет иметь хотя бы один явный цикл. –

0

Под капотом потоки намного сложнее, чем простые массивы. Компиляторы улучшатся, но в настоящее время последовательные для циклов должны быть быстрее, чем потоковые операции.

This article имеет некоторый фон о потоковых трубопроводах, которые используются для реализации потоков. Это может помочь понять сложность этого.

Преимущество потоков заключается в том, что код может быть более четким, и его легче распараллелить.