Когда использовать LinkedList поверх ArrayList в Java? - PullRequest
2841 голосов
/ 27 ноября 2008

Я всегда был одним, чтобы просто использовать:

List<String> names = new ArrayList<>();

Я использую интерфейс в качестве имени типа для переносимость , поэтому, когда я задаю такие вопросы, я могу переделать свой код.

Когда следует использовать LinkedList вместо ArrayList и наоборот?

Ответы [ 31 ]

3121 голосов
/ 27 ноября 2008

Резюме ArrayList с ArrayDeque предпочтительнее в многих больше вариантов использования, чем LinkedList. Если вы не уверены, & mdash; просто начните с ArrayList.


LinkedList и ArrayList являются двумя различными реализациями интерфейса List. LinkedList реализует его с помощью двусвязного списка. ArrayList реализует его с помощью динамически изменяемого размера массива.

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

Для LinkedList<E>

  • get(int index) равно O (n) (при среднем n / 4 шагах)
  • add(E element) - O (1)
  • add(int index, E element) - это O (n) (с в среднем n / 4 шагами), но O (1) при index = 0 <--- основное преимущество <code>LinkedList<E>
  • remove(int index) - это O (n) (при среднем n / 4 шагах)
  • Iterator.remove() - это O (1) . <--- основное преимущество <code>LinkedList<E>
  • ListIterator.add(E element) - это O (1) Это одно из основных преимуществ LinkedList<E>

Примечание. Для многих операций требуется в среднем n / 4 шагов, константа количество шагов в лучшем случае (например, index = 0) и n / 2 шаги в худшем случае (середина списка)

Для ArrayList<E>

  • get(int index) равно O (1) <--- основное преимущество <code>ArrayList<E>
  • add(E element) равно O (1) амортизировано, но O (n) в худшем случае, так как размер массива должен быть изменен и скопирован
  • add(int index, E element) равно O (n) (при среднем n / 2 шагах)
  • remove(int index) равно O (n) (при среднем n / 2 шагах)
  • Iterator.remove() равно O (n) (при среднем n / 2 шагах)
  • ListIterator.add(E element) равно O (n) n / 2 шагами в среднем)

Примечание: для многих операций требуется в среднем n / 2 шагов, константа количество шагов в лучшем случае (конец списка), n шаги в худшем случае (начало списка)

LinkedList<E> допускает вставки или удаления в постоянное время с использованием итераторов , но только последовательный доступ к элементам. Другими словами, вы можете перемещаться по списку вперед или назад, но нахождение позиции в списке занимает время, пропорциональное размеру списка. Javadoc говорит "операции, которые индексируют в списке, будут проходить по списку с начала или конца, в зависимости от того, что ближе" , поэтому эти методы O (n) ( n / 4 шагов) в среднем, хотя O (1) для index = 0.

ArrayList<E>, с другой стороны, разрешить быстрый произвольный доступ для чтения, так что вы можете получить любой элемент за постоянное время. Но добавление или удаление из любого места, кроме конца, требует сдвига всех последних элементов, чтобы сделать отверстие или заполнить пробел. Кроме того, если вы добавляете больше элементов, чем емкость базового массива, выделяется новый массив (в 1,5 раза больше размера), и старый массив копируется в новый, поэтому добавление в ArrayList равно O (n) в худшем случае, но в среднем постоянно.

Таким образом, в зависимости от операций, которые вы намереваетесь выполнить, вы должны выбрать соответствующие реализации. Перебор любого из списков практически одинаково дешев. (Итерация по ArrayList технически быстрее, но если вы не делаете что-то действительно чувствительное к производительности, вам не стоит об этом беспокоиться - они обе константы.)

Основные преимущества использования LinkedList возникают при повторном использовании существующих итераторов для вставки и удаления элементов. Эти операции затем можно выполнить в O (1) , изменив список только локально. В списке массивов остальная часть массива должна быть перемещена (т.е. скопирована). С другой стороны, поиск в LinkedList означает переход по ссылкам в O (n) ( n / 2 шагов) для наихудшего случая, тогда как в ArrayList желаемый позиция может быть вычислена математически и доступна в O (1) .

Другое преимущество использования LinkedList возникает, когда вы добавляете или удаляете из заголовка списка, поскольку эти операции O (1) , тогда как они O (n) для ArrayList. Обратите внимание, что ArrayDeque может быть хорошей альтернативой LinkedList для добавления и удаления из головы, но это не List.

Кроме того, если у вас большие списки, имейте в виду, что использование памяти также отличается. Каждый элемент LinkedList имеет больше накладных расходов, так как указатели на следующий и предыдущий элементы также сохраняются. ArrayLists нет этих накладных расходов. Однако ArrayLists занимает столько памяти, сколько выделено для емкости, независимо от того, были ли элементы фактически добавлены.

Начальная емкость по умолчанию ArrayList довольно мала (10 из Java 1.4 - 1.8). Но поскольку базовая реализация представляет собой массив, размер массива должен быть изменен, если вы добавите много элементов. Чтобы избежать высокой стоимости изменения размера, когда вы знаете, что собираетесь добавить много элементов, создайте ArrayList с более высокой начальной емкостью.

586 голосов
/ 06 октября 2011

До сих пор никто не обращал внимания на объем памяти каждого из этих списков, кроме общего мнения, что LinkedList "намного больше", чем ArrayList, поэтому я сделал некоторые вычисления, чтобы продемонстрировать, насколько точно оба списки занимают N пустых ссылок.

Поскольку в их относительных системах ссылки бывают 32- или 64-битными (даже если они равны нулю), я включил 4 набора данных для 32- и 64-битных LinkedLists и ArrayLists.

Примечание: Размеры, показанные для строк ArrayList, относятся к усеченным спискам - На практике емкость вспомогательного массива в ArrayList обычно больше его Текущее количество элементов.

Примечание 2: (спасибо BeeOnRope) Поскольку CompressedOops теперь используется по умолчанию начиная с середины JDK6 и выше, приведенные ниже значения для 64-битных машин будут в основном соответствовать их 32-битным аналогам, если, конечно, вы специально не отключите его.


Graph of LinkedList and ArrayList No. of Elements x Bytes


Результат ясно показывает, что 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)

222 голосов
/ 27 ноября 2008

ArrayList - это то, что вы хотите. LinkedList почти всегда является ошибкой (производительности).

Почему LinkedList отстой:

  • Он использует множество небольших объектов памяти и, следовательно, влияет на производительность всего процесса.
  • Множество маленьких объектов плохо подходят для кэширования.
  • Любая индексированная операция требует обхода, то есть имеет производительность O (n). Это не очевидно в исходном коде, что приводит к тому, что алгоритмы O (n) работают медленнее, чем если бы использовался ArrayList.
  • Получить хорошую производительность сложно.
  • Даже если производительность big-O такая же, как ArrayList, она все равно будет значительно медленнее.
  • Очень неприятно видеть LinkedList в источнике, потому что это, вероятно, неправильный выбор.
134 голосов
/ 01 января 2011

Как человек, который занимается проектированием операционной производительности в очень крупномасштабных веб-сервисах SOA около десяти лет, я бы предпочел поведение LinkedList, а не ArrayList. Хотя постоянная пропускная способность LinkedList хуже и, следовательно, может привести к покупке большего количества оборудования, поведение ArrayList под давлением может привести к тому, что приложения в кластере будут расширять свои массивы почти синхронно, а для больших размеров массива может привести к отсутствию отзывчивости. в приложении и отключении, находясь под давлением, что является катастрофическим поведением.

Точно так же вы можете получить лучшую пропускную способность в приложении из стандартного сборщика мусора с пропускной способностью по умолчанию, но как только вы получите java-приложения с кучей 10 ГБ, вы можете заблокировать приложение на 25 секунд во время полных сборок мусора, что приводит к тайм-аутам и сбоям в приложениях SOA и разрывает ваши SLA, если это происходит слишком часто. Несмотря на то, что сборщик CMS потребляет больше ресурсов и не достигает той же исходной пропускной способности, это гораздо лучший выбор, поскольку он имеет более предсказуемую и меньшую задержку.

ArrayList - лучший выбор для производительности, если все, что вы подразумеваете под производительностью, это пропускная способность и вы можете игнорировать задержки. По своему опыту на работе я не могу игнорировать задержку в худшем случае.

119 голосов
/ 09 апреля 2010
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

ArrayLists хороши для однократного чтения или добавления, но плохо при добавлении / удалении спереди или по середине.

101 голосов
/ 19 мая 2009

Да, я знаю, это древний вопрос, но я добавлю два моих цента:

LinkedList почти всегда неправильный выбор с точки зрения производительности. Существует несколько очень специфических алгоритмов, для которых требуется LinkedList, но они очень, очень редки, и алгоритм обычно будет конкретно зависеть от способности LinkedList относительно быстро вставлять и удалять элементы в середине списка, как только вы перейдете туда с ListIterator.

Существует один распространенный вариант использования, в котором LinkedList превосходит ArrayList: это вариант очереди. Однако, если ваша цель - производительность, вместо LinkedList вы также должны рассмотреть возможность использования ArrayBlockingQueue (если вы можете заранее определить верхнюю границу размера очереди и можете позволить себе выделить всю память заранее), или это CircularArrayList реализация . (Да, это с 2001 года, поэтому вам нужно будет его обобщить, но я получил сопоставимые коэффициенты производительности с тем, что только что цитировалось в статье в недавней JVM)

57 голосов
/ 27 ноября 2008

Это вопрос эффективности. LinkedList быстро для добавления и удаления элементов, но медленно для доступа к определенному элементу. ArrayList быстр для доступа к определенному элементу, но может медленно добавляться к любому концу и особенно медленно для удаления в середине.

Массив против ArrayList против LinkedList против Vector углубляется в детали Связанный список .

52 голосов
/ 22 сентября 2011

Правильно или неправильно: Пожалуйста, выполните тест на месте и решите сами!

Редактировать / Удалить быстрее в 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);
    }
}
45 голосов
/ 21 апреля 2013

ArrayList по сути массив. LinkedList реализован как двойной связанный список.

get довольно ясно. O (1) для ArrayList, поскольку ArrayList разрешает произвольный доступ с использованием индекса. O (n) для LinkedList, потому что сначала нужно найти индекс. Примечание: существуют разные версии add и remove.

LinkedList быстрее в добавлении и удалении, но медленнее в получении. Вкратце, LinkedList предпочтительнее, если:

  1. нет большого количества произвольного доступа элемента
  2. существует большое количество операций добавления / удаления

=== ArrayList ===

  • добавить (E e)
    • добавить в конце ArrayList
    • требуют изменения размера памяти.
    • O (n) наихудший, O (1) амортизированный
  • add (int index, E element)
    • добавить к определенной позиции индекса
    • требуется смещение и возможная стоимость изменения размера памяти
    • О (п) * * тысяча сорок-четыре
  • удалить (int index)
    • удалить указанный элемент
    • требуется смещение и возможная стоимость изменения размера памяти
    • О (п)
  • удалить (Объект o)
    • удалить первое вхождение указанного элемента из этого списка
    • сначала нужно выполнить поиск элемента, а затем смещение и возможную стоимость изменения размера памяти

=== LinkedList ===

  • add (E e)

    • добавить в конец списка

add (int index, E element)

  • вставить в указанное положение
  • нужно сначала найти позицию
* * Удалить тысячу девяносто-пять ()
  • удалить первый элемент списка
  • O (1)
удалить (int index)
  • удалить элемент с указанным индексом
  • сначала нужно найти элемент
  • О (п)
удалить (Объект o)
  • удалить первое вхождение указанного элемента
  • сначала нужно найти элемент
  • О (п)

Вот рисунок из programcreek.com (add и remove - первый тип, т. Е. Добавить элемент в конец списка и удалить элемент в указанной позиции в список.):

enter image description here

34 голосов
/ 01 марта 2017

Джошуа Блох, автор LinkedList:

Кто-нибудь на самом деле использует LinkedList? Я написал это, и я никогда не использую это.

Ссылка: https://twitter.com/joshbloch/status/583813919019573248

Извините за ответ, который не настолько информативен, как другие ответы, но я подумал, что он будет самым интересным и не требующим пояснений.

...