Различия в производительности между ArrayList и LinkedList

Да, это старая тема, но у меня все еще есть некоторые путаницы.

на Java люди говорят:

  1. ArrayList быстрее LinkedList, если я произвольно обращаюсь к его элементам. Я думаю, что случайный доступ означает "дайте мне n-й элемент". Почему ArrayList быстрее?

  2. LinkedList быстрее, чем ArrayList для удаления. Это я понимаю. ArrayList медленнее, так как внутренний резервный массив должен быть перераспределен. Код объяснение:

    List<String> list = new ArrayList<String>();
    list.add("a");
    list.add("b");
    list.add("c");
    list.remove("b");
    System.out.println(list.get(1)); //output "c"
    
  3. LinkedList быстрее, чем ArrayList для вставки. Что означает вставка здесь? Если это означает переместить некоторые элементы назад, а затем поместить элемент в среднее пустое место, ArrayList должен быть медленнее LinkedList. Если вставка означает только операцию добавления (объекта), как это может быть медленным?

9 ответов


ArrayList быстрее, чем LinkedList, если я случайно получаю доступ к его элементам. Я думаю, что случайный доступ означает "дайте мне n-й элемент". Почему ArrayList быстрее?

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

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

ArrayList медленнее, потому что ему нужно скопировать часть массива, чтобы удалить слот, который стал свободным. Если удаление выполняется с помощью ListIterator.remove() API,LinkedList просто нужно манипулировать несколькими ссылками; если удаление выполняется по значению или индексу,LinkedList должен потенциально сканировать весь список, чтобы найти элемент(Ы), который должен быть удаленный.

если это означает переместить некоторые элементы назад, а затем поместить элемент в среднее пустое место, ArrayList должен быть медленнее.

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


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

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

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

на больше обратная сторона массивов и ArrayList-это фрагмент свободной памяти и перегрузка сборщика мусора. По мере расширения ArrayList создает новые, большие массивы,копирует старый массив в новый и освобождает старый. Память заполняется большими непрерывными кусками свободной памяти, которые недостаточно велики для следующего выделения. В конце концов, для этого нет подходящего места. Несмотря на то, что 90% памяти бесплатно, ни один отдельный кусок не достаточно большой, чтобы сделать эту работу. GC будет работать отчаянно, чтобы переместить вещи вокруг, но если это займет слишком много времени, чтобы изменить пространство, он выбросит OutOfMemoryException. Если он не сдастся, он все равно может замедлить вашу программу.

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

LinkedList использует маленькие, изящные биты памяти, и GC любит его. Он по-прежнему работает нормально, когда вы используете 99% доступных память.

поэтому в общем случае используйте ArrayList для небольших наборов данных, которые вряд ли удалят большую часть их содержимого, или когда у вас есть жесткий контроль над созданием и ростом. (Например, создание одного ArrayList, который использует 90% памяти и использует его без заполнения в течение всей программы, прекрасно. Постоянное создание и освобождение экземпляров ArrayList, использующих 10% памяти, убьет вас.) В противном случае перейдите в LinkedList (или карту, если вам нужна случайная доступ.) Если у вас очень большие коллекции (скажем, более 100 000 элементов), никаких проблем с GC и планирование множества вставок и удалений и никакого случайного доступа, выполните несколько тестов, чтобы увидеть, что быстрее.


The ArrayList class-это класс-оболочка для массива. Он содержит внутренний массив.

public ArrayList<T> {
    private Object[] array;
    private int size;
}

A LinkedList - это класс-оболочка для связанного списка, с внутренним узлом для управления данными.

public LinkedList<T> {
    class Node<T> {
        T data;
        Node next;
        Node prev;
    }
    private Node<T> first;
    private Node<T> last;
    private int size;
}

Примечание, настоящий код используется, чтобы показать, как может быть класс, а не фактическая реализация. Зная, как может быть реализация, мы можем сделать дальнейший анализ:

ArrayList быстрее, чем LinkedList, если я случайно получаю доступ к его элементам. Я думаю, что случайный доступ означает "дайте мне n-й элемент". Почему ArrayList быстрее?

время доступа для ArrayList: O (1). Время доступа к LinkedList: O (n).

в массив, вы можете получить доступ к любому элементу с помощью array[index], в то время как в связанном списке вы должны перемещаться по всему списку, начиная с first пока вы не получите элемент, который вам нужен.

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

время удаления для ArrayList: время доступа + O (n). Время удаления LinkedList: время доступа + O (1).

ArrayList должен переместить все элементы из array[index] до array[index-1] начиная с элемента для удаления индекса. LinkedList должен перемещаться до этого элемента, а затем стереть этот узел, отделив его от списка.

LinkedList быстрее, чем ArrayList для удаления. Я пойми это. ArrayList медленнее, так как внутренний резервный массив должен быть перераспределен.

время вставки для ArrayList: O (n). Время вставки для LinkedList: O (1).

почему ArrayList может принимать O (n)? Потому что, когда вы вставляете новый элемент и массив заполнен, вам нужно создать новый массив с большим размером (вы можете рассчитать новый размер с формулой 2 * size или 3 * size / 2). LinkedList просто добавляет новый узел рядом с последний.

этот анализ не только на Java, но и на других языках программирования, таких как C, C++ и c#.

подробнее здесь:


оба remove() и insert () имеют эффективность выполнения O (n) для обоих ArrayLists и LinkedLists. Однако причина линейного времени обработки исходит из двух очень разных причин:

в ArrayList вы получаете элемент в O(1), но на самом деле удаление или вставка чего-то делает его O (n), потому что все следующие элементы должны быть изменены.

в LinkedList требуется O (n), чтобы фактически добраться до нужного элемента, потому что мы должны начать с с самого начала, пока не достигнем желаемого показателя. Удаление или вставка постоянны, как только мы туда попадем, потому что нам нужно только изменить 1 ссылку для remove() и 2 ссылки для insert().

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

бонус: хотя нет способа сделать эти два метода O (1) для ArrayList, на самом деле есть способ сделать это в LinkedLists. Допустим, мы хотим пройти весь список, удаляя и вставляя элементы на нашем пути. Обычно вы начинаете с самого начала для каждого элемента, используя LinkedList, мы также можем "сохранить" текущий элемент, над которым мы работаем с итератором. С помощью Итератор получаем эффективность O(1) для remove() и insert() при работе в LinkedList. Делая это единственным преимуществом производительности, я знаю, где LinkedList всегда лучше, чем ArrayList.


ответ на 1: ArrayList использует массив под капотом. Доступ к члену объекта ArrayList так же прост, как доступ к массиву по предоставленному индексу, предполагая, что индекс находится в пределах резервного массива. LinkedList должен перебирать свои члены, чтобы добраться до n-го элемента. Это O(n) для LinkedList, против O (1) для ArrayList.


в LinkedList элементы имеют ссылку на элемент до и после него. В ArrayList структура данных-это просто массив.

  1. LinkedList должен перебирать N элементов, чтобы получить N-й элемент. ArrayList должен возвращать только элемент N резервного массива.

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

  3. по той же причине, что и удаление здесь.


ArrayList: ArrayList имеет структуру, подобную массиву, он имеет прямую ссылку на каждый элемент. Таким образом, доступ rendom быстрый в ArrayList.

LinkedList: в LinkedList для получения nth elemnt вам нужно пройти весь список, требуется время по сравнению с ArrayList. Каждый элемент имеет ссылку на предыдущий элемент & nest, поэтому удаление происходит быстро.


ArrayList: класс ArrayList расширяет AbstractList и реализует интерфейс List и RandomAccess (интерфейс маркера). Коллекции поддерживает динамические массивы, которые могут расти по мере необходимости. это дает нам первую итерацию над элементами.

LinkedList: LinkedList упорядочивается по позиции индекса, как ArrayList, за исключением того, что элементы дважды связаны друг с другом. Эта связь дает вам новые методы (помимо того, что вы получаете от Интерфейс списка) для добавления и удаления с начала или конца, что делает его простым выбором для реализации стека или очереди. Имейте в виду, что LinkedList может повторяться медленнее, чем ArrayList,но это хороший выбор, когда вам нужна быстрая вставка и удаление. начиная с Java 5 класс LinkedList был расширен для реализации java.утиль.Интерфейс очереди. Таким образом, теперь он поддерживает общие методы очереди: peek (), poll () и offer ().


даже они кажутся идентичными (тот же реализованный список inteface - не потокобезопасный),они дают разные результаты с точки зрения производительности при добавлении/удалении и поиске времени и потребляемой памяти (LinkedList потребляет больше).

LinkedLists можно использовать, если вы используете высокую вставку / удаление с производительностью O(1). ArrayLists можно использовать, если вы используете операции прямого доступа с производительностью O (1)

этот код может очистить эти комментарии, и вы можете попытаться понять результаты деятельности. (Извините за код плиты котла)

public class Test {

    private static Random rnd;


    static {
        rnd = new Random();
    }


    static List<String> testArrayList;
    static List<String> testLinkedList;
    public static final int COUNT_OBJ = 2000000;

    public static void main(String[] args) {
        testArrayList = new ArrayList<>();
        testLinkedList = new LinkedList<>();

        insertSomeDummyData(testLinkedList);
        insertSomeDummyData(testArrayList);

        checkInsertionPerformance(testLinkedList);  //O(1)
        checkInsertionPerformance(testArrayList);   //O(1) -> O(n)

        checkPerformanceForFinding(testArrayList);  // O(1)
        checkPerformanceForFinding(testLinkedList); // O(n)

    }


    public static void insertSomeDummyData(List<String> list) {
        for (int i = COUNT_OBJ; i-- > 0; ) {
            list.add(new String("" + i));
        }
    }

    public static void checkInsertionPerformance(List<String> list) {

        long startTime, finishedTime;
        startTime = System.currentTimeMillis();
        int rndIndex;
        for (int i = 200; i-- > 0; ) {
            rndIndex = rnd.nextInt(100000);
            list.add(rndIndex, "test");
        }
        finishedTime = System.currentTimeMillis();
        System.out.println(String.format("%s time passed at insertion:%d", list.getClass().getSimpleName(), (finishedTime - startTime)));
    }

    public static void checkPerformanceForFinding(List<String> list) {

        long startTime, finishedTime;
        startTime = System.currentTimeMillis();
        int rndIndex;
        for (int i = 200; i-- > 0; ) {
            rndIndex = rnd.nextInt(100000);
            list.get(rndIndex);
        }
        finishedTime = System.currentTimeMillis();
        System.out.println(String.format("%s time passed at searching:%d", list.getClass().getSimpleName(), (finishedTime - startTime)));

    }

}