Я всегда просто использовал:
List<String> names = new ArrayList<>();
Я использую интерфейс в качестве имени типа для переносимости , чтобы, задавая подобные вопросы, я мог переработать свой код.
Когда следует использовать LinkedList
через ArrayList
и наоборот. наоборот?
27 ответов
До сих пор никто, кажется, не обращал внимания на объем памяти, занимаемый каждым из этих списков, кроме общего консенсуса о том, что LinkedList
«намного больше», чем ArrayList
, поэтому я провел некоторую обработку чисел, чтобы точно продемонстрировать, насколько оба списка занимают N пустых ссылок.
Поскольку в соответствующих системах ссылки 32- или 64-битные (даже если они равны нулю), я включил 4 набора данных для 32- и 64-битных LinkedLists
и ArrayLists
.
Примечание. Размеры, показанные для строк ArrayList
, указаны для обрезанных списков . На практике емкость резервного массива в ArrayList
обычно составляет больше, чем его текущее количество элементов.
Примечание 2: (спасибо BeeOnRope) Поскольку CompressedOops теперь используется по умолчанию со середины JDK6 и выше, приведенные ниже значения для 64-битных машин будут в основном соответствовать их 32-битным аналогам, если только конечно вы его специально выключаете.
Результат ясно показывает, что LinkedList
намного больше, чем ArrayList
, особенно с очень большим количеством элементов. Если важна память, держитесь подальше от LinkedLists
.
Следующие формулы, которые я использовал, дайте мне знать, если я сделал что-то не так, и я исправлю это. «b» равно 4 или 8 для 32- или 64-битных систем, а «n» - количество элементов. Обратите внимание, что причины для модов в том, что все объекты в java будут занимать пространство, кратное 8 байтам, независимо от того, все они используются или нет.
ArrayList:
ArrayList object header + size integer + modCount integer + array reference + (array oject header + b * n) + MOD(array oject, 8) + MOD(ArrayList object, 8) == 8 + 4 + 4 + b + (12 + b * n) + MOD(12 + b * n, 8) + MOD(8 + 4 + 4 + b + (12 + b * n) + MOD(12 + b * n, 8), 8)
LinkedList:
LinkedList object header + size integer + modCount integer + reference to header + reference to footer + (node object overhead + reference to previous element + reference to next element + reference to element) * n) + MOD(node object, 8) * n + MOD(LinkedList object, 8) == 8 + 4 + 4 + 2 * b + (8 + 3 * b) * n + MOD(8 + 3 * b, 8) * n + MOD(8 + 4 + 4 + 2 * b + (8 + 3 * b) * n + MOD(8 + 3 * b, 8) * n, 8)
int
, то есть 4 или 8 байтов данных. В связанном списке, по сути, есть 4 служебных слова. Таким образом, ваш график создает впечатление, что связанные списки используют «пять раз» хранилище списков массивов. Это не правильно. Накладные расходы составляют 16 или 32 байта на объект в качестве дополнительной настройки, а не коэффициента масштабирования.
ArrayList
- это то, что вам нужно. LinkedList
почти всегда является ошибкой (производительности).
Почему LinkedList
отстой:
- Он использует множество небольших объектов памяти и, следовательно, влияет на производительность всего процесса.
- Множество мелких объектов плохо сказываются на местонахождении кеша.
- Любая индексированная операция требует обхода, т.е. имеет производительность O (n). Это не очевидно в исходном коде, что приводит к работе алгоритмов на O (n) медленнее, чем при использовании
ArrayList
. - Получить хорошую производительность сложно.
- Даже когда производительность большого O такая же, как у
ArrayList
, она, вероятно, в любом случае будет значительно медленнее. - Очень неприятно видеть
LinkedList
в исходном тексте, потому что это, вероятно, неправильный выбор.
Как человек, который около десяти лет занимался проектированием эксплуатационных характеристик для очень крупномасштабных веб-сервисов SOA, я бы предпочел поведение LinkedList, а не ArrayList. В то время как стабильная пропускная способность LinkedList хуже и, следовательно, может привести к покупке большего количества оборудования, поведение ArrayList под давлением может привести к тому, что приложения в кластере будут расширять свои массивы почти синхронно, а для массивов больших размеров может привести к недостаточной скорости отклика. в приложении и отключение, находясь под давлением, что является катастрофическим поведением.
Точно так же вы можете повысить пропускную способность в приложении с помощью постоянного сборщика мусора по умолчанию, но как только вы получите java-приложения с кучей 10 ГБ, вы можете заблокировать приложение на 25 секунд во время полных сборщиков мусора, что вызывает тайм-ауты и сбои в приложениях SOA. и нарушает ваши SLA, если это происходит слишком часто. Несмотря на то, что сборщик CMS требует больше ресурсов и не обеспечивает такой же исходной пропускной способности, это гораздо лучший выбор, поскольку он имеет более предсказуемую и меньшую задержку.
ArrayList - лучший выбор для повышения производительности, только если под производительностью вы подразумеваете пропускную способность, и вы можете игнорировать задержку. По моему опыту работы, я не могу игнорировать задержку в наихудшем случае.
ArrayList
нет никаких проблем, поскольку для этого даже не требуется дополнительная память. Учитывая возможность того, что ArrayList
должен увеличить свою емкость один раз в течение всего срока службы веб-службы в качестве причины для снижения производительности с помощью LinkedList
, довольно странно, особенно, если вы сравните стоимость ввода-вывода веб-службы. Увеличение емкости ArrayList
, даже с миллионами элементов, вряд ли будет замечено как задержка.
LinkedList
always выделяет в пять раз больше памяти, чем простой массив ссылок, поэтому ArrayList
, временно требующий 2,5 раза, по-прежнему потребляет гораздо меньше памяти, даже если память не востребован. Поскольку выделение большого массива обходит пространство Eden, оно не влияет на поведение сборщика мусора, если действительно не хватает памяти, и в этом случае LinkedList
взорвался намного раньше ...
Да, я знаю, это древний вопрос, но я добавлю свои два цента:
LinkedList почти всегда является неправильным выбором с точки зрения производительности. Есть несколько очень специфических алгоритмов, для которых требуется LinkedList, но они очень, очень редки, и алгоритм обычно конкретно зависит от способности LinkedList относительно быстро вставлять и удалять элементы в середине списка, как только вы туда переместитесь. с ListIterator.
Существует один распространенный вариант использования, в котором LinkedList превосходит ArrayList: это очередь. Однако, если ваша цель - производительность, вместо LinkedList вам также следует рассмотреть возможность использования ArrayBlockingQueue (если вы можете заранее определить верхнюю границу размера своей очереди и можете позволить себе выделить всю память заранее), или это Реализация CircularArrayList. (Да, это с 2001 года, поэтому вам нужно будет обобщить его, но у меня есть сопоставимые коэффициенты производительности с тем, что цитируется в статье только что в недавней JVM)
ArrayDeque
. docs.oracle.com/javase/6/docs/ api / java / util / ArrayDeque.html
ArrayDeque
медленнее, чем LinkedList
, если все операции не выполняются на одном конце. Это нормально, когда используется в качестве стека, но из этого не получается хорошая очередь.
ArrayDeque
, вероятно, будет быстрее, чем Stack
при использовании в качестве стека, и быстрее, чем LinkedList
при использовании в качестве очереди.
ArrayDeque
.
Это вопрос эффективности. LinkedList
быстро добавляет и удаляет элементы, но медленно получает доступ к определенному элементу. ArrayList
обеспечивает быстрый доступ к определенному элементу, но может быть медленным при добавлении в любой конец и особенно медленным при удалении в середине.
Массив против ArrayList против LinkedList против вектора a> идет более подробно, как и Связанный список.
LinkedList
быстро добавляет / удаляет только первую и последнюю позиции - тогда сложность будет O (1), но добавление в середине все равно будет O (n), потому что нам нужно пробежать примерно n / 2 элементов LinkedList
.
Джошуа Блох, автор LinkedList:
Кто-нибудь действительно использует LinkedList? Я написал это и никогда не использую.
Ссылка: https://twitter.com/joshbloch/status/583813919019573248
Прошу прощения за ответ за то, что он не так информативен, как другие ответы, но я подумал, что он будет наиболее интересным и не требует пояснений.
Правильно или неверно: выполните тест локально и решите сами!
Редактирование / удаление выполняется быстрее в LinkedList
, чем в ArrayList
.
ArrayList
, поддерживаемый Array
, который должен быть вдвое больше, хуже для приложений большого объема.
Ниже приведен результат модульного теста для каждой операции. Время указано в наносекундах.
Operation ArrayList LinkedList
AddAll (Insert) 101,16719 2623,29291
Add (Insert-Sequentially) 152,46840 966,62216
Add (insert-randomly) 36527 29193
remove (Delete) 20,56,9095 20,45,4904
contains (Search) 186,15,704 189,64,981
Вот код:
import org.junit.Assert;
import org.junit.Test;
import java.util.*;
public class ArrayListVsLinkedList {
private static final int MAX = 500000;
String[] strings = maxArray();
////////////// ADD ALL ////////////////////////////////////////
@Test
public void arrayListAddAll() {
Watch watch = new Watch();
List<String> stringList = Arrays.asList(strings);
List<String> arrayList = new ArrayList<String>(MAX);
watch.start();
arrayList.addAll(stringList);
watch.totalTime("Array List addAll() = ");//101,16719 Nanoseconds
}
@Test
public void linkedListAddAll() throws Exception {
Watch watch = new Watch();
List<String> stringList = Arrays.asList(strings);
watch.start();
List<String> linkedList = new LinkedList<String>();
linkedList.addAll(stringList);
watch.totalTime("Linked List addAll() = "); //2623,29291 Nanoseconds
}
//Note: ArrayList is 26 time faster here than LinkedList for addAll()
///////////////// INSERT /////////////////////////////////////////////
@Test
public void arrayListAdd() {
Watch watch = new Watch();
List<String> arrayList = new ArrayList<String>(MAX);
watch.start();
for (String string : strings)
arrayList.add(string);
watch.totalTime("Array List add() = ");//152,46840 Nanoseconds
}
@Test
public void linkedListAdd() {
Watch watch = new Watch();
List<String> linkedList = new LinkedList<String>();
watch.start();
for (String string : strings)
linkedList.add(string);
watch.totalTime("Linked List add() = "); //966,62216 Nanoseconds
}
//Note: ArrayList is 9 times faster than LinkedList for add sequentially
/////////////////// INSERT IN BETWEEN ///////////////////////////////////////
@Test
public void arrayListInsertOne() {
Watch watch = new Watch();
List<String> stringList = Arrays.asList(strings);
List<String> arrayList = new ArrayList<String>(MAX + MAX / 10);
arrayList.addAll(stringList);
String insertString0 = getString(true, MAX / 2 + 10);
String insertString1 = getString(true, MAX / 2 + 20);
String insertString2 = getString(true, MAX / 2 + 30);
String insertString3 = getString(true, MAX / 2 + 40);
watch.start();
arrayList.add(insertString0);
arrayList.add(insertString1);
arrayList.add(insertString2);
arrayList.add(insertString3);
watch.totalTime("Array List add() = ");//36527
}
@Test
public void linkedListInsertOne() {
Watch watch = new Watch();
List<String> stringList = Arrays.asList(strings);
List<String> linkedList = new LinkedList<String>();
linkedList.addAll(stringList);
String insertString0 = getString(true, MAX / 2 + 10);
String insertString1 = getString(true, MAX / 2 + 20);
String insertString2 = getString(true, MAX / 2 + 30);
String insertString3 = getString(true, MAX / 2 + 40);
watch.start();
linkedList.add(insertString0);
linkedList.add(insertString1);
linkedList.add(insertString2);
linkedList.add(insertString3);
watch.totalTime("Linked List add = ");//29193
}
//Note: LinkedList is 3000 nanosecond faster than ArrayList for insert randomly.
////////////////// DELETE //////////////////////////////////////////////////////
@Test
public void arrayListRemove() throws Exception {
Watch watch = new Watch();
List<String> stringList = Arrays.asList(strings);
List<String> arrayList = new ArrayList<String>(MAX);
arrayList.addAll(stringList);
String searchString0 = getString(true, MAX / 2 + 10);
String searchString1 = getString(true, MAX / 2 + 20);
watch.start();
arrayList.remove(searchString0);
arrayList.remove(searchString1);
watch.totalTime("Array List remove() = ");//20,56,9095 Nanoseconds
}
@Test
public void linkedListRemove() throws Exception {
Watch watch = new Watch();
List<String> linkedList = new LinkedList<String>();
linkedList.addAll(Arrays.asList(strings));
String searchString0 = getString(true, MAX / 2 + 10);
String searchString1 = getString(true, MAX / 2 + 20);
watch.start();
linkedList.remove(searchString0);
linkedList.remove(searchString1);
watch.totalTime("Linked List remove = ");//20,45,4904 Nanoseconds
}
//Note: LinkedList is 10 millisecond faster than ArrayList while removing item.
///////////////////// SEARCH ///////////////////////////////////////////
@Test
public void arrayListSearch() throws Exception {
Watch watch = new Watch();
List<String> stringList = Arrays.asList(strings);
List<String> arrayList = new ArrayList<String>(MAX);
arrayList.addAll(stringList);
String searchString0 = getString(true, MAX / 2 + 10);
String searchString1 = getString(true, MAX / 2 + 20);
watch.start();
arrayList.contains(searchString0);
arrayList.contains(searchString1);
watch.totalTime("Array List addAll() time = ");//186,15,704
}
@Test
public void linkedListSearch() throws Exception {
Watch watch = new Watch();
List<String> linkedList = new LinkedList<String>();
linkedList.addAll(Arrays.asList(strings));
String searchString0 = getString(true, MAX / 2 + 10);
String searchString1 = getString(true, MAX / 2 + 20);
watch.start();
linkedList.contains(searchString0);
linkedList.contains(searchString1);
watch.totalTime("Linked List addAll() time = ");//189,64,981
}
//Note: Linked List is 500 Milliseconds faster than ArrayList
class Watch {
private long startTime;
private long endTime;
public void start() {
startTime = System.nanoTime();
}
private void stop() {
endTime = System.nanoTime();
}
public void totalTime(String s) {
stop();
System.out.println(s + (endTime - startTime));
}
}
private String[] maxArray() {
String[] strings = new String[MAX];
Boolean result = Boolean.TRUE;
for (int i = 0; i < MAX; i++) {
strings[i] = getString(result, i);
result = !result;
}
return strings;
}
private String getString(Boolean result, int i) {
return String.valueOf(result) + i + String.valueOf(!result);
}
}
LinkedList
имеет гораздо больше накладных расходов на память, потому что для каждого элемента существует объект узла с пятью полями. Во многих системах это составляет 20 байт накладных расходов. Средние накладные расходы памяти на элемент для ArrayList
составляют полтора слова, что составляет 6 байтов, а в худшем случае - 8 байтов.
removeIf(element -> condition)
там, где он подходит, что может быть значительно быстрее для ArrayList
, по сравнению с циклом и удалением с помощью итератора, поскольку не требуется сдвигать весь остаток для каждого отдельного элемент. Будет ли это работать лучше или хуже, чем LinkedList
, зависит от конкретного сценария, поскольку LinkedList
теоретически составляет O (1), но удаление только одного узла требует нескольких обращений к памяти, которые могут легко превысить число необходимо для ArrayList
при удалении значительного количества элементов.
ArrayList
по сути является массивом. LinkedList
реализован как двусвязный список.
get
довольно ясно. O (1) для ArrayList
, потому что ArrayList
разрешает произвольный доступ с помощью индекса. O (n) для LinkedList
, потому что сначала нужно найти индекс. Примечание: существуют разные версии add
и remove
.
LinkedList
быстрее добавляет и удаляет, но медленнее получает. Короче говоря, LinkedList
следует предпочесть, если:
- нет большого количества произвольного доступа элемента
- есть большое количество операций добавления / удаления
=== ArrayList ===
- добавить (E e)
- добавить в конец ArrayList
- требует затрат на изменение размера памяти.
- O (n) худших, O (1) амортизированных
- добавить (индекс int, элемент E)
- добавить в указанную позицию
- требуется смещение и возможная стоимость изменения размера памяти.
- O (n)
- удалить (индекс int)
- удалить указанный элемент
- требуется смещение и возможная стоимость изменения размера памяти.
- O (n)
- удалить (Объект o)
- удалить первое вхождение указанного элемента из этого списка
- сначала нужно найти элемент, а затем сдвиг и возможную стоимость изменения размера памяти.
- O (n)
=== LinkedList ===
добавить (E e)
- добавить в конец списка
- O (1)
добавить (индекс int, элемент E)
- вставить в указанную позицию
- сначала нужно найти позицию
- O (n)
- удалять()
- удалить первый элемент списка
- O (1)
- удалить (индекс int)
- удалить элемент с указанным индексом
- сначала нужно найти элемент
- O (n)
- удалить (Объект o)
- удалить первое вхождение указанного элемента
- сначала нужно найти элемент
- O (n)
Вот рисунок с сайта programcreek.com (add
и remove
являются первым типом, т. Е. Добавить элемент в конец списка и удалить элемент в указанной позиции в списке.):
TL; DR из-за современной компьютерной архитектуры ArrayList
будет значительно более эффективным практически для любого возможного варианта использования, поэтому LinkedList
следует избегать, за исключением некоторых очень уникальных и экстремальных случаи.
Теоретически LinkedList имеет O (1) для add(E element)
Также очень эффективным должно быть добавление элемента в середину списка.
Практика очень отличается, поскольку LinkedList представляет собой структуру враждебных кеш-данных . С точки зрения производительности: очень мало случаев, когда LinkedList
может быть более эффективным, чем дружественный к кешу ArrayList
.
Вот результаты эталонного тестирования, когда элементы вставляются в случайные места. Как видите, список массивов намного более эффективен, хотя теоретически каждая вставка в середине списка потребует «переместить» n более поздних элементов массива (более низкие значения лучше):
Работая на оборудовании более позднего поколения (более крупные и эффективные кеши) - результаты еще более убедительны:
LinkedList требует гораздо больше времени, чтобы выполнить ту же работу. источник Исходный код
Для этого есть две основные причины:
В основном - узлы
LinkedList
случайным образом разбросаны по памяти. ОЗУ («Память с произвольным доступом») на самом деле не случайна, и блоки памяти необходимо извлекать в кэш. Эта операция требует времени, а когда такие выборки происходят часто - страницы памяти в кеше необходимо постоянно заменять -> Кэш пропускает -> Кэш неэффективен. ЭлементыArrayList
хранятся в непрерывной памяти - это именно то, для чего оптимизирована современная архитектура ЦП.Вторичный
LinkedList
, необходимый для удержания указателей вперед / назад, что означает, что потребление памяти на одно сохраненное значение в 3 раза выше, чем уArrayList
.
Ключевым моментом, который следует запомнить, является то, что стоимость выборки блока памяти более значительна, чем стоимость доступа к одной ячейке памяти. Вот почему считыватель 1 МБ последовательной памяти до x400 раз быстрее, чем чтение такого количества данных из разных блоков памяти:
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
намного более эффективным.
Авторы: Все опубликованные здесь тесты созданы Кьеллом Хедстремом. Еще больше данных можно найти в его блоге
ArrayList
используется собственный метод java.lang.System.arraycopy()
, который написан на C ++ в OpenJDK. Таким образом, хотя теоретически LinkedList
предстоит сделать меньше работы, на практике существует так много экстралингвистических механизмов, которые делают "Большое О" в значительной степени неуместным. В частности, насколько дружественны к кешу вещи в соответствии с этим отличным ответом.
ArrayList
доступен случайным образом, в то время как LinkedList
очень дешево расширять и удалять элементы. В большинстве случаев подойдет ArrayList
.
Если вы не составили большие списки и не измерили узкое место, вам, вероятно, никогда не придется беспокоиться о разнице.
Если в вашем коде есть add(0)
и remove(0)
, используйте LinkedList
и более красивые методы addFirst()
и removeFirst()
. В противном случае используйте ArrayList
.
И, конечно же, Guava ImmutableList - ваш лучший друг.
Я знаю, что это старый пост, но я, честно говоря, не могу поверить, что никто не упомянул, что LinkedList
реализует Deque
. Просто посмотрите на методы в Deque
(и Queue
); если вы хотите честного сравнения, попробуйте запустить LinkedList
с ArrayDeque
и провести сравнение функций.
Давайте сравним LinkedList и ArrayList w.r.t. ниже параметры:
1. Реализация
ArrayList - это реализация интерфейса списка с изменяемым размером массива, а
LinkedList - это реализация интерфейса списка с двусвязным списком.
2. Производительность
get (int index) или поисковая операция
ArrayList get (int index) операция выполняется за постоянное время, то есть O (1), а
LinkedList Время выполнения операции get (int index) - O (n).
Причина того, что ArrayList работает быстрее, чем LinkedList, заключается в том, что ArrayList использует систему на основе индекса для своих элементов, поскольку он внутренне использует структуру данных массива, с другой стороны,
LinkedList не предоставляет доступ на основе индекса для своих элементов, поскольку он выполняет итерацию либо с начала, либо с конца (в зависимости от того, что ближе), чтобы получить узел по указанному индексу элемента.
операция insert () или add (Object)
Вставки в LinkedList обычно выполняются быстрее, чем в ArrayList. В LinkedList добавление или вставка выполняется за O (1).
В ArrayList , если массив является полным, т.е. в худшем случае, существует дополнительная стоимость изменения размера массива и копирования элементов в новый массив, что делает время выполнения операции добавления в ArrayList O ( n), иначе O (1).
операция remove (int)
Операция удаления в LinkedList обычно аналогична ArrayList, то есть O (n).
В LinkedList есть два перегруженных метода удаления. один - remove () без каких-либо параметров, который удаляет заголовок списка и выполняется за постоянное время O (1). Другой перегруженный метод удаления в LinkedList - это remove (int) или remove (Object), который удаляет объект или int, переданный в качестве параметра. Этот метод просматривает LinkedList до тех пор, пока не найдет объект и не отключит его от исходного списка. Следовательно, время выполнения этого метода - O (n).
В то время как в ArrayList метод remove (int) включает копирование элементов из старого массива в новый обновленный массив, следовательно, его время выполнения - O (n).
3. Обратный итератор
LinkedList можно перебирать в обратном направлении с помощью функции declndingIterator (), в то время как
в ArrayList нет спускающегосяIterator (), поэтому нам нужно написать собственный код для итерации по ArrayList в обратном направлении.
4. Начальная емкость
Если конструктор не перегружен, ArrayList создает пустой список с начальной емкостью 10, а
LinkedList создает только пустой список без начальной емкости.
5. Накладные расходы на память
Накладные расходы на память в LinkedList больше по сравнению с ArrayList, поскольку узел в LinkedList должен поддерживать адреса следующего и предыдущего узла. В то время как
В ArrayList каждый индекс содержит только фактический объект (данные).
Вот нотация Big-O как в ArrayList
, так и в LinkedList
, а также в CopyOnWrite-ArrayList
:
< Сильный > ArrayList
get O(1)
add O(1)
contains O(n)
next O(1)
remove O(n)
iterator.remove O(n)
LinkedList
get O(n)
add O(1)
contains O(n)
next O(1)
remove O(1)
iterator.remove O(1)
CopyOnWrite-ArrayList
get O(1)
add O(n)
contains O(n)
next O(1)
remove O(n)
iterator.remove O(n)
Исходя из этого, вы должны решить, что выбрать. :)
LinkedList.add()
, хотя об этом говорится в большинстве ответов.
Помимо других хороших аргументов, приведенных выше, вы должны заметить, что ArrayList
реализует интерфейс RandomAccess
, а LinkedList
реализует Queue
.
Таким образом, они каким-то образом решают несколько разные проблемы, с разницей в эффективности и поведении (см. Их список методов).
Это зависит от того, какие операции вы будете выполнять в Списке больше.
ArrayList
обеспечивает более быстрый доступ к индексированному значению. Гораздо хуже при вставке или удалении объектов.
Чтобы узнать больше, прочитайте любую статью, в которой говорится о разнице между массивами и связанными списками.
Список массивов - это, по сути, массив с методами добавления элементов и т. Д. (Вместо этого вы должны использовать общий список). Это набор элементов, к которым можно получить доступ через индексатор (например, [0]). Он подразумевает переход от одного пункта к другому.
Связанный список определяет переход от одного элемента к следующему (элемент a -> элемент b). Вы можете получить тот же эффект со списком массивов, но связанный список точно говорит, какой элемент должен следовать за предыдущим.
См. Руководства по Java - Список реализаций.
Важная особенность связного списка (которую я не читал в другом ответе) - это объединение двух списков. С массивом это O (n) (+ накладные расходы на некоторые перераспределения) со связанным списком это только O (1) или O (2) ;-)
Важно : для Java это LinkedList
неверно! См. Есть ли в Java метод быстрого объединения для связанного списка?
next
из одного списка на первый узел во втором списке. Единственный способ - использовать addAll()
, который добавляет элементы последовательно, хотя это лучше, чем цикл и вызов add()
для каждого элемента. Чтобы сделать это быстро в O (1), вам понадобится класс компоновки (например, org.apache.commons.collections.collection.CompositeCollection), но тогда это будет работать для любого типа List / Collection.
У ArrayList и LinkedList есть свои плюсы и минусы.
ArrayList использует непрерывный адрес памяти по сравнению с LinkedList, который использует указатели на следующий узел. Поэтому, когда вы хотите найти элемент в ArrayList, это быстрее, чем выполнить n итераций с LinkedList.
С другой стороны, вставка и удаление в LinkedList намного проще, потому что вам просто нужно изменить указатели, тогда как ArrayList подразумевает использование операции сдвига для любой вставки или удаления.
Если в вашем приложении часто выполняются операции извлечения, используйте ArrayList. Если вы часто вставляете и удаляете, используйте LinkedList.
1) Основная структура данных
Первое различие между ArrayList и LinkedList заключается в том, что ArrayList поддерживается Array, а LinkedList поддерживается LinkedList. Это приведет к дальнейшим различиям в производительности.
2) LinkedList реализует Deque
Еще одно различие между ArrayList и LinkedList заключается в том, что помимо интерфейса List, LinkedList также реализует интерфейс Deque, который обеспечивает операции «первым пришел - первым обслужен» для add()
и poll()
, а также несколько других функций Deque. 3) Добавление элементов в ArrayList Добавление элемента в ArrayList - это операция O (1), если она не запускает изменение размера массива, и в этом случае он становится O (log (n)). С другой стороны, добавление элемента в LinkedList - это операция O (1), поскольку она не требует навигации.
4) Удаление элемента из позиции
Чтобы удалить элемент из определенного индекса, например вызывая remove(index)
, ArrayList выполняет операцию копирования, которая приближает его к O (n), в то время как LinkedList необходимо перейти к этой точке, что также делает его O (n / 2), поскольку он может перемещаться в любом направлении в зависимости от близость.
5) Итерация по ArrayList или LinkedList
Итерация - это операция O (n) как для LinkedList, так и для ArrayList, где n - номер элемента.
6) Получение элемента из позиции
Операция get(index)
- это O (1) в ArrayList, а ее O (n / 2) в LinkedList, так как она должна пройти до этой записи. Хотя в нотации Big O O (n / 2) - это просто O (n), потому что мы игнорируем там константы.
7) Память
LinkedList использует объект-оболочку Entry, который представляет собой статический вложенный класс для хранения данных и двух узлов, следующего и предыдущего, в то время как ArrayList просто хранит данные в массиве.
Таким образом, в случае ArrayList потребность в памяти кажется меньше, чем в LinkedList, за исключением случая, когда Array выполняет операцию изменения размера при копировании содержимого из одного массива в другой.
Если массив достаточно велик, он может занять много памяти в этот момент и вызвать сборку мусора, что может замедлить время отклика.
Из всех вышеперечисленных различий между ArrayList и LinkedList, похоже, что ArrayList - лучший выбор, чем LinkedList почти во всех случаях, за исключением случаев, когда вы выполняете более частую операцию add()
, чем remove()
или get()
.
Связанный список легче изменить, чем ArrayList, особенно если вы добавляете или удаляете элементы с начала или с конца, потому что связанный список внутренне хранит ссылки на эти позиции, и они доступны за время O (1).
Другими словами, вам не нужно перемещаться по связанному списку, чтобы достичь позиции, в которую вы хотите добавить элементы, в этом случае добавление становится операцией O (n). Например, вставка или удаление элемента в середине связанного списка.
На мой взгляд, для большинства практических целей в Java лучше использовать ArrayList вместо LinkedList.
Я прочитал ответы, но есть один сценарий, в котором я всегда использую LinkedList вместо ArrayList, которым я хочу поделиться, чтобы услышать мнения:
Каждый раз, когда у меня был метод, который возвращает список данных, полученных из БД, я всегда использую LinkedList.
Мое объяснение заключалось в том, что поскольку невозможно точно знать, сколько результатов я получаю, память не будет потрачена впустую (как в ArrayList с разницей между емкостью и фактическим количеством элементов), и не будет потрачено времени на попытки продублируйте емкость.
Что касается ArrayList, я согласен с тем, что, по крайней мере, вы всегда должны использовать конструктор с начальной емкостью, чтобы минимизировать дублирование массивов в максимально возможной степени.
Операция get (i) в ArrayList выполняется быстрее, чем LinkedList, потому что:
ArrayList: Реализация интерфейса List с изменяемым размером массива
LinkedList: реализация двусвязного списка интерфейсов List и Deque.
Операции, которые индексируют в списке, будут проходить по списку с начала или конца, в зависимости от того, что ближе к указанному индексу.
Один из тестов, которые я видел здесь, проводит тест только один раз. Но я заметил, что вам нужно запускать эти тесты много раз, и в конечном итоге их времена сойдутся. В основном JVM нужно разогреть. Для моего конкретного случая использования мне нужно было добавлять / удалять элементы в список, который вырастает примерно до 500 элементов. В моих тестах LinkedList
вышел быстрее, с LinkedList
около 50 000 NS и ArrayList
около 90 000 NS… плюс-минус. См. Код ниже.
public static void main(String[] args) {
List<Long> times = new ArrayList<>();
for (int i = 0; i < 100; i++) {
times.add(doIt());
}
System.out.println("avg = " + (times.stream().mapToLong(x -> x).average()));
}
static long doIt() {
long start = System.nanoTime();
List<Object> list = new LinkedList<>();
//uncomment line below to test with ArrayList
//list = new ArrayList<>();
for (int i = 0; i < 500; i++) {
list.add(i);
}
Iterator it = list.iterator();
while (it.hasNext()) {
it.next();
it.remove();
}
long end = System.nanoTime();
long diff = end - start;
//uncomment to see the JVM warmup and get faster for the first few iterations
//System.out.println(diff)
return diff;
}
И remove()
, и insert()
имеют эффективность выполнения O (n) как для ArrayLists, так и для LinkedLists. Однако причина линейного времени обработки кроется в двух очень разных причинах:
В ArrayList вы переходите к элементу в O (1), но на самом деле удаление или вставка чего-либо делает его O (n), потому что все следующие элементы необходимо изменить.
В LinkedList для фактического перехода к желаемому элементу требуется O (n), потому что мы должны начинать с самого начала, пока не достигнем желаемого индекса. На самом деле удаление или вставка - это константа, потому что нам нужно изменить только 1 ссылку для remove()
и 2 ссылки для insert()
.
Какой из двух быстрее вставлять и удалять, зависит от того, где это происходит. Если мы приблизимся к началу, LinkedList будет быстрее, потому что нам нужно пройти через относительно небольшое количество элементов. Если мы приблизимся к концу, ArrayList будет быстрее, потому что мы доберемся до него за постоянное время и нам нужно будет изменить только несколько оставшихся элементов, которые следуют за ним. Если сделать это точно посередине, LinkedList будет быстрее, потому что прохождение n элементов быстрее, чем перемещение n значений.
Бонус: хотя нет возможности сделать эти два метода O (1) для ArrayList, на самом деле есть способ сделать это в LinkedLists. Допустим, мы хотим пройти весь список, удаляя и вставляя элементы по пути. Обычно вы начинаете с самого начала для каждого элемента, используя LinkedList, мы также можем «сохранить» текущий элемент, над которым мы работаем, с помощью Iterator. С помощью итератора мы получаем эффективность O (1) для remove()
и insert()
при работе в LinkedList. Это единственное преимущество в производительности, которое я знаю, где LinkedList всегда лучше, чем ArrayList.
ArrayList расширяет AbstractList и реализует интерфейс списка. ArrayList - это динамический массив.
Можно сказать, что он в основном создан для преодоления недостатков массивов
Класс LinkedList расширяет AbstractSequentialList и реализует интерфейс List, Deque и Queue.
Производительность
arraylist.get()
- это O (1), тогда как linkedlist.get()
- это O (n)
arraylist.add()
равно O (1), а linkedlist.add()
равно 0 (1)
arraylist.contains()
- это O (n), а linkedlist.contains()
- это O (n)
arraylist.next()
равно O (1) и linkedlist.next()
равно O (1)
arraylist.remove()
равно O (n), тогда как linkedlist.remove()
равно O (1)
В Arraylist
iterator.remove()
- O (n)
, а в связанном списке
iterator.remove()
- O (1)
Я обычно использую один над другим в зависимости от временной сложности операций, которые я выполняю с этим конкретным списком.
|---------------------|---------------------|--------------------|------------|
| Operation | ArrayList | LinkedList | Winner |
|---------------------|---------------------|--------------------|------------|
| get(index) | O(1) | O(n) | ArrayList |
| | | n/4 steps in avg | |
|---------------------|---------------------|--------------------|------------|
| add(E) | O(1) | O(1) | LinkedList |
| |---------------------|--------------------| |
| | O(n) in worst case | | |
|---------------------|---------------------|--------------------|------------|
| add(index, E) | O(n) | O(n) | LinkedList |
| | n/2 steps | n/4 steps | |
| |---------------------|--------------------| |
| | | O(1) if index = 0 | |
|---------------------|---------------------|--------------------|------------|
| remove(index, E) | O(n) | O(n) | LinkedList |
| |---------------------|--------------------| |
| | n/2 steps | n/4 steps | |
|---------------------|---------------------|--------------------|------------|
| Iterator.remove() | O(n) | O(1) | LinkedList |
| ListIterator.add() | | | |
|---------------------|---------------------|--------------------|------------|
|--------------------------------------|-----------------------------------|
| ArrayList | LinkedList |
|--------------------------------------|-----------------------------------|
| Allows fast read access | Retrieving element takes O(n) |
|--------------------------------------|-----------------------------------|
| Adding an element require shifting | o(1) [but traversing takes time] |
| all the later elements | |
|--------------------------------------|-----------------------------------|
| To add more elements than capacity |
| new array need to be allocated |
|--------------------------------------|
Похожие вопросы
Новые вопросы
java
Java - это язык программирования высокого уровня. Используйте этот тег, если у вас возникли проблемы с использованием или пониманием самого языка. Этот тег редко используется отдельно и чаще всего используется вместе с [spring], [spring-boot], [jakarta-ee], [android], [javafx], [hadoop], [gradle] и [maven].