#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])
это самое простое, наименее подверженное ошибкам и наиболее эффективное решение.