Есть ли какая-либо разница в производительности в массиве против stream.toArray в java

#java #arrays #list #java-8 #java-stream

#java #массивы #Список #java-8 #java-stream

Вопрос:

Мне нужно преобразовать список идентификаторов в массив идентификаторов. Я могу сделать это многими способами, но не уверен, какой из них следует использовать.

Скажем,

 1. ids.stream().toArray(Id[]::new)
2. ids.toArray(new Id[ids.length])
  

Какой из них более эффективен и почему?

Комментарии:

1. Если вы на самом деле не хотите использовать stream API, я не могу придумать веской причины для использования первого подхода. Я не могу представить, что разница в производительности оправдала бы снижение читаемости кода.

2. У вас действительно есть проблемы с производительностью? О скольких списках мы говорим? Разве этого преобразования нельзя избежать в первую очередь?

3. Во-первых: что вы подразумеваете под эффективным? меньше памяти, меньше процессора, быстрее? Второй: достаточно ли велик ваш массив, чтобы действительно что-то изменить? Если нет, используйте более читаемый, а не более эффективный.

4. Эффективный означает, что скорость выполнения выше. Массив может содержать 6 миллионов записей

5. @Eugene это не то, что я сказал, поскольку я согласен с вами, более читаемый не является самым быстрым. Я имею в виду, что вы должны расставлять приоритеты в том, что вы кодируете. Если мы говорим о массиве из 20 записей, такую оптимизацию не следует рассматривать, и мы должны использовать более читаемый. Если (как OP) вы используете миллионы данных, тогда оптимизация становится интересной и может сыграть ведущую роль в удобочитаемости (то есть когда комментирующий код становится полезным)

Ответ №1:

представлена java-11 Collection::toArray , которая имеет эту реализацию:

 default <T> T[] toArray(IntFunction<T[]> generator) {
    return toArray(generator.apply(0));
}
  

Чтобы упростить в вашем случае, он фактически выполняет : ids.toArray(new Id[0]) ; то есть — он не указывает общий ожидаемый размер.

Это быстрее, чем указывать размер, и это неинтуитивно; но связано с тем фактом, что если JVM может доказать, что выделяемый вами массив будет переопределен некоторым копированием, за которым немедленно следует копирование, ему не нужно выполнять начальное обнуление массива, и это оказывается быстрее, чем указание начального размера (где обнуление должно произойти).

Потоковый подход будет иметь (или пытаться угадать оценку) начальный размер, который будут вычислять внутренние компоненты потока, потому что:

  ids.stream().toArray(Id[]::new)
  

на самом деле:

  ids.stream().toArray(size -> Id[size]);
  

и это size либо известно, либо оценено, основываясь на внутренних характеристиках, которые Spliterator имеет. Если поток сообщает о SIZED характеристике (как в вашем простом случае), то это легко, size всегда известно. С другой стороны, если этого SIZED нет, у stream internals будет только оценка того, сколько элементов будет присутствовать, и в таком случае для захвата элементов будет использоваться скрытая новая коллекция, вызываемая SpinedBuffer .

Вы можете прочитать больше здесь, но подход ids.toArray(new Id[0]) будет самым быстрым.

Комментарии:

1. Не каждый Collection создает SIZED поток, т. Е. одновременные коллекции не будут. Но для параллельных коллекций ids.toArray(new Id[ids.size()]) было бы даже нарушено, если приложение не может исключить одновременные изменения во время операции. Таким образом, это сводится к тому, что ids.toArray(new Id[0]) это самое простое, наименее подверженное ошибкам и наиболее эффективное решение.