TL; DR из-за современной компьютерной архитектуры ArrayList
будет значительно более эффективным практически для любого возможного варианта использования - и поэтому следует избегать LinkedList
, за исключением некоторых очень уникальных и экстремальных случаев.
Теоретически, LinkedList имеет O (1) для add(E element)
Добавление элемента в середине списка также должно быть очень эффективным.
Практика сильно отличается, так как LinkedList представляет собой Cache Hostile Структура данных. От производительности POV - очень мало случаев, когда LinkedList
мог бы быть лучше, чем Cache-friendly ArrayList
.
Вот результаты тестового тестирования вставки элементов в случайных местах. Как видите, список массивов гораздо эффективнее, хотя теоретически каждая вставка в середине списка потребует «переместить» более поздние элементы массива n (более низкие значения лучше):
Работа на оборудовании более позднего поколения (большие, более эффективные кэши) - результаты еще более убедительны:
LinkedList занимает гораздо больше времени для выполнения той же работы. source Исходный код
Для этого есть две основные причины:
В основном - что узлы LinkedList
случайно разбросаны по памяти. ОЗУ («Память с произвольным доступом») на самом деле не является случайным, и блоки памяти должны быть извлечены для кэширования. Эта операция занимает много времени, и когда такие выборки происходят часто - страницы памяти в кеше необходимо постоянно заменять -> Cache misses -> Cache неэффективен.
ArrayList
элементов хранятся в непрерывной памяти - именно для этого оптимизируется современная архитектура ЦП.
Вторичный LinkedList
, необходимый для удержания указателей назад / вперед, что означает 3-кратное потребление памяти на одно сохраненное значение по сравнению с ArrayList
.
DynamicIntArray , кстати, является пользовательской реализацией ArrayList, содержащей Int
(примитивный тип), а не объекты - следовательно, все данные действительно хранятся смежно - следовательно, даже более эффективно.
Ключевым элементом, который следует помнить, является то, что стоимость выборки блока памяти более значительна, чем стоимость доступа к одной ячейке памяти. Вот почему считыватель последовательной памяти объемом 1 МБ работает в х400 раз быстрее, чем считывает этот объем данных из разных блоков памяти:
Latency Comparison Numbers (~2012)
----------------------------------
L1 cache reference 0.5 ns
Branch mispredict 5 ns
L2 cache reference 7 ns 14x L1 cache
Mutex lock/unlock 25 ns
Main memory reference 100 ns 20x L2 cache, 200x L1 cache
Compress 1K bytes with Zippy 3,000 ns 3 us
Send 1K bytes over 1 Gbps network 10,000 ns 10 us
Read 4K randomly from SSD* 150,000 ns 150 us ~1GB/sec SSD
Read 1 MB sequentially from memory 250,000 ns 250 us
Round trip within same datacenter 500,000 ns 500 us
Read 1 MB sequentially from SSD* 1,000,000 ns 1,000 us 1 ms ~1GB/sec SSD, 4X memory
Disk seek 10,000,000 ns 10,000 us 10 ms 20x datacenter roundtrip
Read 1 MB sequentially from disk 20,000,000 ns 20,000 us 20 ms 80x memory, 20X SSD
Send packet CA->Netherlands->CA 150,000,000 ns 150,000 us 150 ms
Источник: Числа задержки, которые должен знать каждый программист
Просто, чтобы еще яснее понять, пожалуйста, проверьте эталон добавления элементов в начало списка. Это тот случай использования, когда теоретически LinkedList
должен действительно сиять, а ArrayList
должен давать плохие или даже худшие результаты:
Примечание: это тест C ++ Std lib, но мой предыдущий опыт показал, что результаты C ++ и Java очень похожи. Исходный код
Копирование последовательного объема памяти - это операция, оптимизированная современными ЦП - изменяющая теория и фактически делающая, опять же, ArrayList
/ Vector
намного более эффективной
Кредиты: Все опубликованные тесты созданы Kjell Hedström . Еще больше данных можно найти в его блоге