Я всегда просто использовал:
List<String> names = new ArrayList<>();
Я использую интерфейс в качестве имени типа для переносимости, чтобы, когда я задаю подобные вопросы, я мог переработать свой код.
Когда следует использовать LinkedList
через ArrayList
и наоборот. наоборот?
29 ответов
Это вопрос эффективности. LinkedList
быстро добавляет и удаляет элементы, но медленно получает доступ к определенному элементу. ArrayList
обеспечивает быстрый доступ к определенному элементу, но может быть медленным при добавлении в любой конец и особенно медленным при удалении в середине.
Массив против ArrayList против LinkedList против вектора a> идет более подробно, как и Связанный список.
LinkedList
быстро добавляет / удаляет только первую и последнюю позиции - тогда сложность будет O (1), но добавление в середине все равно будет O (n), потому что нам нужно пробежать примерно n / 2 элементов LinkedList
.
Правильно или неверно: выполните тест локально и решите сами!
Редактирование / удаление выполняется быстрее в 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)
- добавить в определенную позицию индекса
- требует смещения и возможных затрат на изменение размера памяти
- О(n)
- удалить (индекс int)
- удалить указанный элемент
- требует смещения и возможных затрат на изменение размера памяти
- О(n)
- удалить (Объект o)
- удалить первое вхождение указанного элемента из этого списка
- нужно сначала найти элемент, а затем сдвинуть и возможно изменить размер памяти
- О(n)
=== LinkedList ===
добавить (E e)
- добавить в конец списка
- О(1)
добавить (индекс int, элемент E)
- вставить в указанную позицию
- нужно сначала найти позицию
- О(n)
- удалять()
- удалить первый элемент списка
- О(1)
- удалить (индекс int)
- удалить элемент с указанным индексом
- нужно сначала найти элемент
- О(n)
- удалить (Объект 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 - ваш лучший друг.
Я обычно использую один над другим в зависимости от временной сложности операций, которые я выполняю с этим конкретным списком.
|---------------------|---------------------|--------------------|------------|
| 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 |
|--------------------------------------|
Давайте сравним 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), который удаляет 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 каждый индекс содержит только фактический объект (данные).
Я знаю, что это старый пост, но я, честно говоря, не могу поверить, что никто не упомянул, что LinkedList
реализует Deque
. Просто посмотрите на методы в Deque
(и Queue
); если вы хотите честного сравнения, попробуйте запустить LinkedList
с ArrayDeque
и провести сравнение функций.
Вот нотация 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
обеспечивает более быстрый доступ к индексированному значению. Гораздо хуже при вставке или удалении объектов.
Чтобы узнать больше, прочитайте любую статью, в которой говорится о разнице между массивами и связанными списками.
Список массивов - это, по сути, массив с методами добавления элементов и т. Д. (Вместо этого вы должны использовать общий список). Это набор элементов, к которым можно получить доступ через индексатор (например, [0]). Он подразумевает переход от одного пункта к другому.
Связанный список определяет переход от одного элемента к следующему (элемент a -> элемент b). Вы можете получить тот же эффект со списком массивов, но связанный список точно говорит, какой элемент должен следовать за предыдущим.
Важная особенность связного списка (которую я не читал в другом ответе) - это объединение двух списков. С массивом это 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, я согласен с тем, что, по крайней мере, вы всегда должны использовать конструктор с начальной емкостью, чтобы минимизировать дублирование массивов в максимально возможной степени.
LinkedList
имеет гораздо более высокие накладные расходы на элемент (3 указателя на элемент). ArrayList
имеет 1 указатель на элемент. Таким образом, даже если ArrayList
заполнен только наполовину, у него никогда не будет больше накладных расходов, чем LinkedList
.
ArrayList
и LinkedList
реализуют List interface
, а их методы и результаты почти идентичны. Однако между ними есть несколько различий, которые делают один лучше другого в зависимости от требований.
ArrayList против LinkedList
1) Операция поиска Search:
ArrayList
выполняется довольно быстро по сравнению с операцией поиска LinkedList
. get(int index)
в ArrayList
дает производительность O(1)
, а производительность LinkedList
равна O(n)
.
Reason:
ArrayList
поддерживает систему индексов для своих элементов, поскольку неявно использует структуру данных массива, что ускоряет поиск элемента в списке. С другой стороны, LinkedList
реализует двусвязный список, который требует обхода всех элементов для поиска элемента.
2) Deletion:
LinkedList
операция удаления дает O(1)
производительность, а ArrayList
дает переменную производительность: O(n)
в худшем случае (при удалении первого элемента) и O(1)
в лучшем случае (при удалении последнего элемента).
Вывод: удаление элемента LinkedList выполняется быстрее по сравнению с ArrayList.
Причина: каждый элемент LinkedList поддерживает два указателя (адреса), которые указывают на оба соседних элемента в списке. Следовательно, удаление требует только изменения местоположения указателя в двух соседних узлах (элементах) узла, который будет удален. В то время как в ArrayList все элементы должны быть сдвинуты, чтобы заполнить пространство, созданное удаленным элементом.
3) Метод добавления Inserts Performance:
LinkedList
дает O(1)
производительность, а ArrayList
дает O(n)
в худшем случае. Причина та же, что и для удаления.
4) Memory Overhead:
ArrayList
поддерживает индексы и данные элементов, а LinkedList
поддерживает данные элементов и два указателя на соседние узлы.
следовательно, потребление памяти в LinkedList сравнительно велико.
Между этими классами есть несколько сходств, которые заключаются в следующем:
- И ArrayList, и LinkedList являются реализацией интерфейса List.
- Они оба поддерживают порядок вставки элементов, что означает, что при отображении элементов ArrayList и LinkedList результирующий набор будет иметь тот же порядок, в котором элементы были вставлены в список.
- Оба этих класса не синхронизированы, и их можно явно синхронизировать с помощью метода Collections.synchronizedList.
iterator
иlistIterator
, возвращаемые этими классами, представляют собойfail-fast
(если список структурно изменен в любое время после создания итератора, любым способом, кроме как посредствомiterator’s
собственного удаления или добавить методы, итератор будетthrow
aConcurrentModificationException
).
Когда использовать LinkedList и когда использовать ArrayList?
- Как объяснялось выше, операции вставки и удаления дают хорошую производительность.
(O(1))
вLinkedList
по сравнению сArrayList(O(n))
.Следовательно, если в приложении требуется частое добавление и удаление, лучшим выбором будет LinkedList.
- Поиск (
get method
) операции выполняются быстро вArraylist (O(1))
но не вLinkedList (O(n))
поэтому, если требуется меньше операций добавления и удаления и больше операций поиска, ArrayList будет вашим лучшим выбором.
Операция get (i) в ArrayList выполняется быстрее, чем LinkedList, потому что:
ArrayList: Реализация интерфейса List с изменяемым размером массива
LinkedList: реализация двусвязного списка интерфейсов List и Deque.
Операции, которые индексируют в списке, будут проходить по списку с начала или конца, в зависимости от того, что ближе к указанному индексу.
И 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.
Один из тестов, которые я видел здесь, проводит тест только один раз. Но я заметил, что вам нужно запускать эти тесты много раз, и в конечном итоге их времена сойдутся. В основном 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;
}
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)
Помимо других хороших аргументов, приведенных выше, вы должны заметить, что ArrayList
реализует интерфейс RandomAccess
, а LinkedList
реализует Queue
.
Таким образом, они каким-то образом решают несколько разные проблемы, с разницей в эффективности и поведении (см. Их список методов).
См. Руководства по Java - Список реализаций.
До сих пор никто, кажется, не обращал внимания на объем памяти, занимаемый каждым из этих списков, кроме общего консенсуса о том, что 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
в исходном тексте, потому что это, вероятно, неправильный выбор.
Algorithm ArrayList LinkedList
seek front O(1) O(1)
seek back O(1) O(1)
seek to index O(1) O(N)
insert at front O(N) O(1)
insert at back O(1) O(1)
insert after an item O(N) O(1)
Алгоритмы: нотация Big-Oh < / a> (в архиве)
ArrayLists хороши для однократной записи и многократного чтения или добавлений, но плохи при добавлении / удалении спереди или посередине.
O(1)
. Чтобы найти точку вставки, нужно пройти половину списка.
LinkedList
является O(1)
, если у вас есть итератор для позиции вставки , т.е. ListIterator.add
предположительно O(1)
для LinkedList
.
Как человек, который около десяти лет занимался проектированием эксплуатационных характеристик для очень крупномасштабных веб-сервисов SOA, я бы предпочел поведение LinkedList, а не ArrayList. В то время как стабильная пропускная способность LinkedList хуже и, следовательно, может привести к покупке большего количества оборудования, поведение ArrayList под давлением может привести к тому, что приложения в кластере будут расширять свои массивы почти синхронно, а для массивов больших размеров может привести к недостаточной скорости отклика. в приложении и отключение, находясь под давлением, что является катастрофическим поведением.
Точно так же вы можете повысить пропускную способность в приложении с помощью постоянного сборщика мусора по умолчанию, но как только вы получите java-приложения с кучей 10 ГБ, вы можете заблокировать приложение на 25 секунд во время полных сборщиков мусора, что вызывает тайм-ауты и сбои в приложениях SOA. и нарушает ваши SLA, если это происходит слишком часто. Несмотря на то, что сборщик CMS требует больше ресурсов и не обеспечивает такой же исходной пропускной способности, это гораздо лучший выбор, поскольку он имеет более предсказуемую и меньшую задержку.
ArrayList - лучший выбор для повышения производительности, только если под производительностью вы подразумеваете пропускную способность, и вы можете игнорировать задержку. По моему опыту работы, я не могу игнорировать задержку в наихудшем случае.
Обновление (27 августа 2021 г. - 10 лет спустя): этот ответ (мой исторически наиболее популярный ответ на SO) скорее всего неверен (по причинам, изложенным в комментариях ниже). Я хотел бы добавить, что ArrayList оптимизирует последовательное чтение памяти и минимизирует промахи строки кэша, TLB и т. д. Накладные расходы на копирование, когда массив выходит за границы, вероятно, несущественны по сравнению (и могут быть выполнены с помощью эффективных операций ЦП). ). Этот ответ также, вероятно, со временем становится все хуже, учитывая тенденции в области аппаратного обеспечения. Единственные ситуации, в которых LinkedList может иметь смысл, — это что-то очень надуманное, когда у вас есть тысячи списков, каждый из которых может вырасти до размера ГБ, но где нельзя сделать никаких хороших предположений во время выделения списка и его установки. все до размера ГБ взорвет кучу. И если вы обнаружили такую проблему, то она действительно требует реинжиниринга, каким бы ни было ваше решение (и я не люблю легкомысленно предлагать реинжиниринг старого кода, потому что сам поддерживаю груды и кучи старого кода, но это было бы очень хороший случай, когда первоначальный дизайн просто исчерпал взлетно-посадочную полосу и его нужно выбросить). Тем не менее, я все равно оставлю свое мнение, которому уже несколько десятков лет, чтобы вы его прочитали. Просто, логично и довольно неправильно.
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:
Кто-нибудь действительно использует LinkedList? Я написал это и никогда не использую.
Ссылка: https://twitter.com/joshbloch/status/583813919019573248
Мне жаль, что ответ не был таким информативным, как другие ответы, но я подумал, что он будет наиболее понятным, если не показательным.
Похожие вопросы
Связанные вопросы
Новые вопросы
java
Java — это высокоуровневый объектно-ориентированный язык программирования. Используйте этот тег, если у вас возникли проблемы с использованием или пониманием самого языка. Этот тег часто используется вместе с другими тегами для библиотек и/или фреймворков, используемых разработчиками Java.
std::vector
(например, JavaArrayList
) иstd::list
(например, JavaLinkedList
).