Когда я должен использовать список против LinkedList

393

Когда лучше использовать List против LinkedList ?

Джонатан Аллен
источник
3
Java q , не должен сильно отличаться.
Nawfal
1
@ jonathan-allen, пожалуйста, подумайте об изменении принятого ответа. Текущий является неточным и чрезвычайно вводящим в заблуждение.
Xpleria

Ответы:

107

редактировать

Пожалуйста, прочитайте комментарии к этому ответу. Люди утверждают, что я не сделал надлежащие тесты. Я согласен, что это не должно быть принятым ответом. Когда я учился, я провел несколько тестов и хотел поделиться ими.

Оригинальный ответ ...

Я нашел интересные результаты:

// Temporary class to show the example
class Temp
{
    public decimal A, B, C, D;

    public Temp(decimal a, decimal b, decimal c, decimal d)
    {
        A = a;            B = b;            C = c;            D = d;
    }
}

Связанный список (3,9 секунды)

        LinkedList<Temp> list = new LinkedList<Temp>();

        for (var i = 0; i < 12345678; i++)
        {
            var a = new Temp(i, i, i, i);
            list.AddLast(a);
        }

        decimal sum = 0;
        foreach (var item in list)
            sum += item.A;

Список (2,4 секунды)

        List<Temp> list = new List<Temp>(); // 2.4 seconds

        for (var i = 0; i < 12345678; i++)
        {
            var a = new Temp(i, i, i, i);
            list.Add(a);
        }

        decimal sum = 0;
        foreach (var item in list)
            sum += item.A;

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




Вот еще одно сравнение, выполняющее много вставок (мы планируем вставить элемент в середине списка)

Связанный список (51 секунда)

        LinkedList<Temp> list = new LinkedList<Temp>();

        for (var i = 0; i < 123456; i++)
        {
            var a = new Temp(i, i, i, i);

            list.AddLast(a);
            var curNode = list.First;

            for (var k = 0; k < i/2; k++) // In order to insert a node at the middle of the list we need to find it
                curNode = curNode.Next;

            list.AddAfter(curNode, a); // Insert it after
        }

        decimal sum = 0;
        foreach (var item in list)
            sum += item.A;

Список (7,26 секунд)

        List<Temp> list = new List<Temp>();

        for (var i = 0; i < 123456; i++)
        {
            var a = new Temp(i, i, i, i);

            list.Insert(i / 2, a);
        }

        decimal sum = 0;
        foreach (var item in list)
            sum += item.A;

Связанный список, имеющий ссылку на место, куда вставить (0,04 секунды)

        list.AddLast(new Temp(1,1,1,1));
        var referenceNode = list.First;

        for (var i = 0; i < 123456; i++)
        {
            var a = new Temp(i, i, i, i);

            list.AddLast(a);
            list.AddBefore(referenceNode, a);
        }

        decimal sum = 0;
        foreach (var item in list)
            sum += item.A;

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

Тоно Нам
источник
99
Одно из преимуществ LinkedList перед List (это специфично для .net): поскольку список поддерживается внутренним массивом, он размещается в одном непрерывном блоке. Если размер выделенного блока превышает 85000 байт, он будет размещен в куче больших объектов, некомпактное поколение. В зависимости от размера это может привести к фрагментации кучи, легкой форме утечки памяти.
JerKimball
35
Обратите внимание, что если вы много делаете предоплату (как вы это делаете в последнем примере) или удаляете первую запись, связанный список почти всегда будет значительно быстрее, так как не нужно выполнять поиск или перемещение / копирование. Список потребует перемещения всего на определенное место, чтобы разместить новый элемент, делая операцию O (N).
Чао
6
Почему цикл list.AddLast(a);в последних двух примерах LinkedList? Я делаю это один раз перед циклом, как list.AddLast(new Temp(1,1,1,1));в следующем за последним LinkedList, но мне кажется, что вы добавляете вдвое больше объектов Temp в самих циклах. (И когда я дважды проверяю себя в тестовом приложении , конечно же, вдвое больше, чем в LinkedList.)
ruffin
7
Я понизил этот ответ. 1) Ваш общий совет I say never use a linkedList.ошибочен, как показывает ваш последующий пост. Вы можете отредактировать его. 2) Какое у вас время? Инстанцирование, сложение и перечисление всего за один шаг? Главным образом, создание экземпляров и перечисление - это не то, о чем беспокоятся люди, это одноразовые шаги. В частности, сроки вставки и дополнения даст лучшую идею. 3) Самое главное, вы добавляете больше, чем требуется, в связанный список. Это неправильное сравнение. Распространяет неправильное представление о связном списке.
nawfal
47
Извините, но этот ответ действительно плохой. Пожалуйста, НЕ слушайте этот ответ. Причина в двух словах: совершенно ошибочно думать, что реализации списков на основе массива достаточно глупы, чтобы изменять размер массива при каждой вставке. Связанные списки, естественно, медленнее списков на основе массива при обходе, а также при вставке с обоих концов, потому что только им нужно создавать новые объекты, в то время как списки на основе массива используют буфер (очевидно, в обоих направлениях). (Плохо сделанные) тесты указывают именно на это. Ответ полностью не проверяет случаи, в которых связанные списки предпочтительнее!
Мафу
277

В большинстве случаев List<T>это более полезно. LinkedList<T>будет стоить меньше при добавлении / удалении элементов в середине списка, тогда как List<T>добавление / удаление в конце списка будет дешевым .

LinkedList<T>только в наиболее эффективном случае, если вы обращаетесь к последовательным данным (в прямом или обратном направлении) - произвольный доступ является относительно дорогим, поскольку он должен обходить цепочку каждый раз (следовательно, поэтому у него нет индексатора). Тем не менее, поскольку a List<T>по сути является просто массивом (с оберткой), произвольный доступ - это нормально.

List<T>также предлагает много методов поддержки - Find, ToArrayи т.д.; тем не менее, они также доступны для LinkedList<T>.NET 3.5 / C # 3.0 с помощью методов расширения, так что это менее важный фактор.

Марк Гравелл
источник
4
Одно из преимуществ List <> по сравнению с LinkedList <>, о котором я никогда не задумывался, касается того, как микропроцессоры реализуют кэширование памяти. Хотя я не совсем понимаю это, автор этой статьи блога много говорит о «местонахождении ссылки», которая делает обход массива намного быстрее, чем обход связанного списка, по крайней мере, если связанный список стал несколько фрагментированным в памяти , kjellkod.wordpress.com/2012/02/25/…
RenniePet
@RenniePet List реализован с помощью динамического массива, а массивы являются непрерывными блоками памяти.
Кейси
2
Поскольку List - это динамический массив, поэтому иногда полезно указывать емкость List в конструкторе, если вы знаете это заранее.
Кардин Ли Дж. Х.
Возможно ли, что реализация C, all, array, List <T> и LinkedList <T> несколько неоптимальна для одного очень важного случая: вам нужен очень большой список, append (AddLast) и последовательный обход (в одном направлении) совершенно нормально: я не хочу изменять размер массива для получения непрерывных блоков (гарантируется ли это для каждого массива, даже для массивов по 20 ГБ?), и я заранее не знаю размер, но я могу заранее угадать размер блока, например, 100 МБ резервировать каждый раз заранее. Это было бы хорошей реализацией. Или массив / список похож на это, и я пропустил точку?
Филм
1
@Philm - это сценарий, в котором вы пишете свою собственную шим над выбранной стратегией блока; List<T>и T[]потерпит неудачу за то, что он слишком длинный (все одна плита), LinkedList<T>будет плачем за то, что он слишком гранулированный (плита за элемент).
Марк Гравелл
212

Представление связанного списка как списка может быть немного обманчивым. Это больше похоже на цепь. На самом деле в .NET LinkedList<T>даже не реализуют IList<T>. В связанном списке нет реальной концепции индекса, хотя может показаться, что она есть. Конечно, ни один из методов, предоставленных в классе, не принимает индексы.

Связанные списки могут быть односвязными или двусвязными. Это относится к тому, имеет ли каждый элемент в цепочке ссылку только на следующий (односвязный) или на оба предыдущих / следующих элемента (двусвязный). LinkedList<T>вдвойне связан.

Внутренне, List<T>поддерживается массивом. Это обеспечивает очень компактное представление в памяти. И наоборот, LinkedList<T>включает в себя дополнительную память для хранения двунаправленных связей между последовательными элементами. Таким образом, объем занимаемой памяти LinkedList<T>обычно будет больше, чем для List<T>(с оговоркой, которая List<T>может иметь неиспользуемые элементы внутреннего массива для повышения производительности во время операций добавления).

Они также имеют разные характеристики производительности:

Append

  • LinkedList<T>.AddLast(item) постоянное время
  • List<T>.Add(item) амортизированное постоянное время, линейный наихудший случай

Prepend

  • LinkedList<T>.AddFirst(item) постоянное время
  • List<T>.Insert(0, item) линейное время

вставка

  • LinkedList<T>.AddBefore(node, item) постоянное время
  • LinkedList<T>.AddAfter(node, item) постоянное время
  • List<T>.Insert(index, item) линейное время

Удаление

  • LinkedList<T>.Remove(item) линейное время
  • LinkedList<T>.Remove(node) постоянное время
  • List<T>.Remove(item) линейное время
  • List<T>.RemoveAt(index) линейное время

подсчитывать

  • LinkedList<T>.Count постоянное время
  • List<T>.Count постоянное время

Содержит

  • LinkedList<T>.Contains(item) линейное время
  • List<T>.Contains(item) линейное время

Очистить

  • LinkedList<T>.Clear() линейное время
  • List<T>.Clear() линейное время

Как видите, они в основном эквивалентны. На практике API LinkedList<T>более громоздок в использовании, и детали его внутренних потребностей выливаются в ваш код.

Однако, если вам нужно сделать много вставок / удалений из списка, он предлагает постоянное время. List<T>предлагает линейное время, так как дополнительные элементы в списке должны быть перемешаны после вставки / удаления.

Дрю Ноакс
источник
2
Является ли количество связанных списков постоянным? Я думал, что это будет линейным?
Иан Баллард
10
@ Iain, счетчик кэшируется в обоих классах списка.
Дрю Ноакс
3
Вы написали, что «List <T> .Add (item) logarithmic time», однако на самом деле это «Constant», если емкость списка может хранить новый элемент, и «Linear», если список не имеет достаточно места и нового быть перераспределенным.
Странник
@aStranger, конечно, ты прав. Не уверен, что я думал выше - возможно, что нормальное время амортизации является логарифмическим, а это не так. На самом деле амортизированное время постоянно. Я не попал в лучший / худший случай операций, стремясь к простому сравнению. Я думаю, что операция добавления достаточно важна, чтобы предоставить эту деталь, однако. Буду редактировать ответ. Спасибо.
Дрю Ноакс
1
@Philm, возможно, вам следует начать новый вопрос, и вы не скажете, как вы собираетесь использовать эту структуру данных после ее создания, но если вы говорите миллион строк, вам может понадобиться какой-то гибрид (связанный список куски массива или аналогичные) для уменьшения фрагментации кучи, уменьшения накладных расходов памяти и исключения одного огромного объекта в LOH.
Дрю Ноакс
118

Связанные списки обеспечивают очень быструю вставку или удаление члена списка. Каждый член в связанном списке содержит указатель на следующий член в списке, поэтому для вставки члена в позицию i:

  • обновить указатель в элементе i-1, чтобы он указывал на новый элемент
  • установить указатель в новом элементе, чтобы он указывал на элемент i

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

b3.
источник
6
Я хотел бы добавить, что связанные списки имеют накладные расходы на элемент, сохраненные выше, через LinkedListNode, который ссылается на предыдущий и следующий узел. Выгода от того, что это непрерывный блок памяти, не требуется для хранения списка, в отличие от списка на основе массива.
paulecoyote
3
Разве непрерывный блок памяти обычно не отрабатывается?
Джонатан Аллен
7
Да, непрерывный блок предпочтителен для производительности произвольного доступа и потребления памяти, но для коллекций, которым необходимо регулярно менять размер, структуру, такую ​​как массив, обычно нужно копировать в новое место, тогда как связанный список должен управлять памятью только для недавно вставленные / удаленные узлы.
jpierson
6
Если вам когда-либо приходилось работать с очень большими массивами или списками (список просто оборачивает массив), у вас начнутся проблемы с памятью, даже если на вашем компьютере достаточно памяти. Список использует стратегию удвоения, когда он выделяет новое пространство в своем базовом массиве. Таким образом, заполненный массив 1000000 elemnt будет скопирован в новый массив с 2000000 элементами. Этот новый массив должен быть создан в непрерывном пространстве памяти, достаточно большом для его хранения.
Андрей
1
У меня был конкретный случай, когда все, что я делал, это добавлял и удалял, и зацикливал один за другим ... здесь связанный список намного превосходил обычный список ...
Питер
26

Мой предыдущий ответ был недостаточно точным. Как верно было ужасно: D Но теперь я могу опубликовать гораздо более полезный и правильный ответ.


Я сделал несколько дополнительных тестов. Вы можете найти его источник по следующей ссылке и заново проверить его в своей среде: https://github.com/ukushu/DataStructuresTestsAndOther.git

Краткие результаты:

  • Массив нужно использовать:

    • Так часто, как это возможно. Это быстро и занимает наименьший диапазон оперативной памяти для того же объема информации.
    • Если вы знаете точное количество клеток, необходимых
    • Если данные сохранены в массиве <85000 b (85000/32 = 2656 элементов для целочисленных данных)
    • При необходимости высокая скорость произвольного доступа
  • Список нужно использовать:

    • При необходимости добавить ячейки в конец списка (часто)
    • При необходимости добавить ячейки в начале / середине списка (НЕ ЧАСТО)
    • Если данные сохранены в массиве <85000 b (85000/32 = 2656 элементов для целочисленных данных)
    • При необходимости высокая скорость произвольного доступа
  • LinkedList нужно использовать:

    • При необходимости добавить ячейки в начале / середине / конце списка (часто)
    • При необходимости только последовательный доступ (вперед / назад)
    • Если вам нужно сохранить БОЛЬШИЕ предметы, но количество предметов мало.
    • Лучше не использовать для большого количества элементов, так как он использует дополнительную память для ссылок.

Больше деталей:

введите сюда изображение Интересно знать:

  1. LinkedList<T>внутренне это не список в .NET. Это даже не реализовать IList<T>. И именно поэтому отсутствуют индексы и методы, связанные с индексами.

  2. LinkedList<T>коллекция на основе указателя узла. В .NET это в двусвязной реализации. Это означает, что предыдущие / следующие элементы имеют ссылку на текущий элемент. И данные фрагментированы - разные объекты списка могут быть расположены в разных местах оперативной памяти. Также будет использовано больше памяти, LinkedList<T>чем для List<T>или для массива.

  3. List<T>в .Net является альтернативой Java ArrayList<T>. Это означает, что это оболочка массива. Таким образом, он выделяется в памяти как один непрерывный блок данных. Если выделенный размер данных превышает 85000 байт, он будет перемещен в кучу больших объектов. В зависимости от размера это может привести к фрагментации кучи (легкая форма утечки памяти). Но в то же время, если размер <85000 байт - это обеспечивает очень компактное и быстрое представление в памяти.

  4. Один непрерывный блок предпочтителен для производительности произвольного доступа и потребления памяти, но для коллекций, которым необходимо регулярно менять размер, структуру, такую ​​как массив, обычно необходимо копировать в новое место, тогда как связанный список должен управлять только памятью для вновь вставленного / удаленные узлы.

Эндрю
источник
1
Вопрос: Под "данными, сохраненными в массиве <или> 85.000 байт" вы имеете в виду данные на массив / список ELEMENT, не так ли? Можно понять, что вы имеете в виду размер данных всего массива ..
Филм,
Элементы массива расположены последовательно в памяти. Так по массиву. Я знаю об ошибке в таблице, позже я ее исправлю :) (надеюсь ....)
Андрей
Если при вставке списки выполняются медленно, то если список имеет большой оборот (много операций вставки / удаления), занимает ли память занимаемое удаленное пространство, и если это так, ускоряет ли повторную вставку?
Роб
18

Разница между List и LinkedList заключается в их базовой реализации. Список - это массив на основе массива (ArrayList). LinkedList - это основанная на указателе узла коллекция (LinkedListNode). Что касается использования уровня API, то оба они в значительной степени одинаковы, поскольку оба реализуют один и тот же набор интерфейсов, таких как ICollection, IEnumerable и т. Д.

Ключевое различие возникает, когда производительность имеет значение. Например, если вы реализуете список со сложной операцией «INSERT», LinkedList превосходит List. Поскольку LinkedList может сделать это за O (1) раз, но List может потребоваться расширить размер базового массива. Для получения дополнительной информации вы можете прочитать об алгоритмическом различии между LinkedList и структурами данных массива. http://en.wikipedia.org/wiki/Linked_list и Array

Надеюсь, это поможет,

user23117
источник
4
Список <T> основан на массиве (T []), а не на ArrayList. Повторно вставьте: изменение размера массива не является проблемой (алгоритм удвоения означает, что большую часть времени ему не нужно это делать): проблема заключается в том, что он должен сначала скопировать все существующие данные, что занимает немного время.
Марк Гравелл
2
@Marc, «алгоритм удвоения» только делает его O (logN), но он все еще хуже, чем O (1)
Илья Рыженков
2
Моя точка зрения заключалась в том, что боль вызывает не изменение размера, а блядь. В худшем случае, если мы добавляем первый (нулевой) элемент каждый раз, то блит должен каждый раз все перемещать.
Марк Гравелл
@IlyaRyzhenkov - вы думаете о случае, когда Addвсегда в конце существующего массива. Listдостаточно «хорош», даже если не O (1). Серьезная проблема возникает, если вам нужно много Adds, которых нет в конце. Марк отмечает, что необходимость перемещать существующие данные каждый раз, когда вы вставляете (а не только когда требуется изменение размера), является более существенной потерей производительности List.
ToolmakerSteve
Проблема в том, что теоретические обозначения Big O не рассказывают всю историю. В компьютерных науках это все, что кого-либо когда-либо заботит, но в реальном мире беспокоиться гораздо больше, чем это.
MattE
11

Основное преимущество связанных списков над массивами состоит в том, что ссылки дают нам возможность эффективно переупорядочивать элементы. Седжвик, р. 91

Доктор алрави
источник
1
ИМО это должен быть ответ. LinkedList используются, когда важен гарантированный заказ.
RBaarda
1
@RBaarda: я не согласен. Это зависит от уровня, о котором мы говорим. Алгоритмический уровень отличается от уровня реализации машины. Для рассмотрения скорости вам понадобится и последнее. Как уже указывалось, массивы реализованы как «один кусок» памяти, что является ограничением, поскольку это может привести к изменению размера и реорганизации памяти, особенно с очень большими массивами. Подумав немного, особая собственная структура данных, связанный список массивов будет одной из идей, чтобы лучше контролировать скорость линейного заполнения и доступ к очень большим структурам данных.
Филм
1
@Philm - я одобрил ваш комментарий, но хотел бы отметить, что вы описываете другое требование. Ответ говорит о том, что связанный список имеет преимущество в производительности для алгоритмов, которые включают в себя большое переупорядочение элементов. Учитывая это, я интерпретирую комментарий RBaarda как ссылку на необходимость добавлять / удалять элементы при постоянном поддержании заданного порядка (критериев сортировки). Так что не просто "линейная начинка". Учитывая это, List проигрывает, потому что индексы бесполезны (меняются каждый раз, когда вы добавляете элемент в любом месте, кроме как в конце).
ToolmakerSteve
4

Обычное обстоятельство использования LinkedList выглядит следующим образом:

Предположим, вы хотите удалить много определенных строк из списка строк большого размера, скажем, 100 000. Строки для удаления можно найти в HashSet dic, и список строк, как полагают, содержит от 30000 до 60000 таких строк для удаления.

Тогда какой тип списка лучше всего подходит для хранения 100 000 строк? Ответ LinkedList. Если они хранятся в ArrayList, то итерация по нему и удаление совпадающих строк может занять до миллиардов операций, в то время как с помощью итератора и метода remove () требуется всего около 100 000 операций.

LinkedList<String> strings = readStrings();
HashSet<String> dic = readDic();
Iterator<String> iterator = strings.iterator();
while (iterator.hasNext()){
    String string = iterator.next();
    if (dic.contains(string))
    iterator.remove();
}
Том
источник
6
Вы можете просто использовать RemoveAllдля удаления элементы из, Listне перемещая много элементов, или использовать Whereиз LINQ для создания второго списка. Однако использование LinkedListздесь приводит к значительному увеличению объема памяти по сравнению с другими типами коллекций, а потеря локальности памяти означает, что итерация будет заметно медленнее, что делает его немного хуже, чем a List.
Обслуживание
@ Служите, обратите внимание, что в ответе Тома используется Java. Я не уверен, есть ли RemoveAllэквивалент в Java.
Артуро Торрес Санчес
3
@ ArturoTorresSánchez Ну, вопрос, в частности, гласит, что речь идет о .NET, так что просто делает ответ гораздо менее подходящим.
Servy
@ Сервис, тогда ты должен был упомянуть об этом с самого начала.
Артуро Торрес Санчес
Если RemoveAllнедоступно для List, вы можете выполнить алгоритм «уплотнения», который будет выглядеть как цикл Тома, но с двумя индексами и необходимостью перемещать элементы, которые будут храниться по одному во внутреннем массиве списка. Эффективность равна O (n), так же, как алгоритм Тома для LinkedList. В обеих версиях время для вычисления ключа HashSet для строк доминирует. Это не хороший пример того, когда использовать LinkedList.
ToolmakerSteve
2

Когда вам нужен встроенный индексированный доступ, сортировка (и после этого двоичного поиска) и метод «ToArray ()», вы должны использовать List.

Михаил Даматов
источник
2

По сути, List<>в .NET это обертка над массивом . А LinkedList<> является связанным списком . Таким образом, вопрос сводится к тому, какова разница между массивом и связанным списком, и когда следует использовать массив вместо связанного списка. Вероятно, два наиболее важных фактора в вашем решении, которые следует использовать, сводятся к следующему:

  • Связанные списки имеют намного лучшую производительность вставки / удаления, если только вставки / удаления не находятся на последнем элементе в коллекции. Это связано с тем, что массив должен сдвигать все оставшиеся элементы, которые идут после точки вставки / удаления. Однако, если вставка / удаление находится в конце списка, этот сдвиг не требуется (хотя может потребоваться изменить размер массива, если его емкость превышена).
  • Массивы имеют гораздо лучшие возможности доступа. Массивы могут быть проиндексированы напрямую (в постоянное время). Связанные списки должны быть пройдены (линейное время).
iliketocode
источник
1

Это адаптировано из принятого ответа Тоно Нама, исправляющего несколько неправильных измерений в нем.

Тест:

static void Main()
{
    LinkedListPerformance.AddFirst_List(); // 12028 ms
    LinkedListPerformance.AddFirst_LinkedList(); // 33 ms

    LinkedListPerformance.AddLast_List(); // 33 ms
    LinkedListPerformance.AddLast_LinkedList(); // 32 ms

    LinkedListPerformance.Enumerate_List(); // 1.08 ms
    LinkedListPerformance.Enumerate_LinkedList(); // 3.4 ms

    //I tried below as fun exercise - not very meaningful, see code
    //sort of equivalent to insertion when having the reference to middle node

    LinkedListPerformance.AddMiddle_List(); // 5724 ms
    LinkedListPerformance.AddMiddle_LinkedList1(); // 36 ms
    LinkedListPerformance.AddMiddle_LinkedList2(); // 32 ms
    LinkedListPerformance.AddMiddle_LinkedList3(); // 454 ms

    Environment.Exit(-1);
}

И код:

using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;

namespace stackoverflow
{
    static class LinkedListPerformance
    {
        class Temp
        {
            public decimal A, B, C, D;

            public Temp(decimal a, decimal b, decimal c, decimal d)
            {
                A = a; B = b; C = c; D = d;
            }
        }



        static readonly int start = 0;
        static readonly int end = 123456;
        static readonly IEnumerable<Temp> query = Enumerable.Range(start, end - start).Select(temp);

        static Temp temp(int i)
        {
            return new Temp(i, i, i, i);
        }

        static void StopAndPrint(this Stopwatch watch)
        {
            watch.Stop();
            Console.WriteLine(watch.Elapsed.TotalMilliseconds);
        }

        public static void AddFirst_List()
        {
            var list = new List<Temp>();
            var watch = Stopwatch.StartNew();

            for (var i = start; i < end; i++)
                list.Insert(0, temp(i));

            watch.StopAndPrint();
        }

        public static void AddFirst_LinkedList()
        {
            var list = new LinkedList<Temp>();
            var watch = Stopwatch.StartNew();

            for (int i = start; i < end; i++)
                list.AddFirst(temp(i));

            watch.StopAndPrint();
        }

        public static void AddLast_List()
        {
            var list = new List<Temp>();
            var watch = Stopwatch.StartNew();

            for (var i = start; i < end; i++)
                list.Add(temp(i));

            watch.StopAndPrint();
        }

        public static void AddLast_LinkedList()
        {
            var list = new LinkedList<Temp>();
            var watch = Stopwatch.StartNew();

            for (int i = start; i < end; i++)
                list.AddLast(temp(i));

            watch.StopAndPrint();
        }

        public static void Enumerate_List()
        {
            var list = new List<Temp>(query);
            var watch = Stopwatch.StartNew();

            foreach (var item in list)
            {

            }

            watch.StopAndPrint();
        }

        public static void Enumerate_LinkedList()
        {
            var list = new LinkedList<Temp>(query);
            var watch = Stopwatch.StartNew();

            foreach (var item in list)
            {

            }

            watch.StopAndPrint();
        }

        //for the fun of it, I tried to time inserting to the middle of 
        //linked list - this is by no means a realistic scenario! or may be 
        //these make sense if you assume you have the reference to middle node

        //insertion to the middle of list
        public static void AddMiddle_List()
        {
            var list = new List<Temp>();
            var watch = Stopwatch.StartNew();

            for (var i = start; i < end; i++)
                list.Insert(list.Count / 2, temp(i));

            watch.StopAndPrint();
        }

        //insertion in linked list in such a fashion that 
        //it has the same effect as inserting into the middle of list
        public static void AddMiddle_LinkedList1()
        {
            var list = new LinkedList<Temp>();
            var watch = Stopwatch.StartNew();

            LinkedListNode<Temp> evenNode = null, oddNode = null;
            for (int i = start; i < end; i++)
            {
                if (list.Count == 0)
                    oddNode = evenNode = list.AddLast(temp(i));
                else
                    if (list.Count % 2 == 1)
                        oddNode = list.AddBefore(evenNode, temp(i));
                    else
                        evenNode = list.AddAfter(oddNode, temp(i));
            }

            watch.StopAndPrint();
        }

        //another hacky way
        public static void AddMiddle_LinkedList2()
        {
            var list = new LinkedList<Temp>();
            var watch = Stopwatch.StartNew();

            for (var i = start + 1; i < end; i += 2)
                list.AddLast(temp(i));
            for (int i = end - 2; i >= 0; i -= 2)
                list.AddLast(temp(i));

            watch.StopAndPrint();
        }

        //OP's original more sensible approach, but I tried to filter out
        //the intermediate iteration cost in finding the middle node.
        public static void AddMiddle_LinkedList3()
        {
            var list = new LinkedList<Temp>();
            var watch = Stopwatch.StartNew();

            for (var i = start; i < end; i++)
            {
                if (list.Count == 0)
                    list.AddLast(temp(i));
                else
                {
                    watch.Stop();
                    var curNode = list.First;
                    for (var j = 0; j < list.Count / 2; j++)
                        curNode = curNode.Next;
                    watch.Start();

                    list.AddBefore(curNode, temp(i));
                }
            }

            watch.StopAndPrint();
        }
    }
}

Вы можете увидеть результаты в соответствии с теоретическими характеристиками, которые другие документировали здесь. Совершенно ясно - LinkedList<T>выигрывает в случае вставок. Я не проверял удаление из середины списка, но результат должен быть таким же. Конечно, List<T>есть и другие области, где он работает лучше, чем O (1) произвольный доступ.

Навфал
источник
0

Используйте LinkedList<>когда

  1. Вы не знаете, сколько объектов проходит через ворота затопления. Например, Token Stream.
  2. Когда вы хотели только удалить \ вставить в конце.

Для всего остального лучше использовать List<>.

Энтони Томас
источник
6
Я не понимаю, почему пункт 2 имеет смысл. Связанные списки хороши, когда вы делаете много вставок / удалений по всему списку.
Дрю Ноакс
Из-за того, что LinkedLists не основаны на индексах, вам действительно нужно сканировать весь список для вставки или удаления, что влечет за собой штраф O (n). List <>, с другой стороны, страдает от изменения размера массива, но все же, IMO, является лучшим вариантом по сравнению с LinkedLists.
Энтони Томас
1
Вам не нужно сканировать список для вставок / удалений, если вы отслеживаете LinkedListNode<T>объекты в вашем коде. Если вы можете сделать это, то это гораздо лучше, чем использовать List<T>, особенно для очень длинных списков, где часто вставляются / удаляются записи.
Дрю Ноакс
Вы имеете в виду хеш-таблицу? Если это так, то это был бы типичный компромисс между пространством и временем, когда каждый программист должен сделать выбор на основе проблемной области :) Но да, это сделало бы это быстрее.
Энтони Томас,
1
@AntonyThomas - Нет, он подразумевает передачу ссылок на узлы вместо передачи ссылок на элементы . Если все, что у вас есть, это элемент , то и List, и LinkedList имеют плохую производительность, потому что вам приходится искать. Если вы думаете «но со списком, я могу просто передать индекс»: это действительно только тогда, когда вы никогда не вставляете новый элемент в середину списка. LinkedList не имеет этого ограничения, если вы держитесь за узел (и используете node.Valueвсякий раз, когда хотите оригинальный элемент). Таким образом, вы переписываете алгоритм для работы с узлами, а не с необработанными значениями.
ToolmakerSteve
0

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

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

  1. Предположим, вы просматриваете элементы и хотите выполнить много вставок / удалений; LinkedList делает это за линейное O (n) время, тогда как List делает это за квадратичное O (n ^ 2) время.
  2. Предположим, что вы хотите снова и снова получать доступ к более крупным объектам, LinkedList стал очень полезным.
  3. Deque () и queue () лучше реализованы с помощью LinkedList.
  4. Увеличение размера LinkedList намного проще и лучше, если вы имеете дело со многими и более крупными объектами.

Надеюсь, кто-то найдет эти комментарии полезными.

Абхишек
источник
Обратите внимание, что этот совет для .NET, а не Java. В реализации связанного списка Java у вас нет понятия «текущий узел», поэтому вы должны просматривать список для каждой вставки.
Джонатан Аллен
Этот ответ верен только частично: 2) если элементы большие, то сделайте тип элемента классом, а не структурой, чтобы список просто содержал ссылку. Тогда размер элемента становится неактуальным. 3) Удаление и очередь могут быть эффективно выполнены в List, если вы используете List в качестве «кольцевого буфера» вместо выполнения вставки или удаления при запуске. Стивен Клири Дэк . 4) частично верно: когда много объектов, про LL не требуется огромная непрерывная память; Недостатком является дополнительная память для указателей узлов.
ToolmakerSteve
-2

Так много средних ответов здесь ...

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

Используйте связанные списки, когда

1) Вы хотите безопасность потока. Вы можете создавать более безопасные многопоточные алгоритмы. Затраты на блокировку будут доминировать в списке одновременных стилей.

2) Если у вас есть большие очереди, подобные структурам, и вы хотите удалить или добавить где угодно, но конец всегда. > 100К списков существует, но не так часто.

user1496062
источник
3
Этот вопрос касался двух реализаций C #, а не связанных списков в целом.
Джонатан Аллен
Одинаково на каждом языке
user1496062
-2

Я спросил похожий вопрос, связанный с производительностью коллекции LinkedList , и обнаружил, что реализация Deque Стивена Клири на C # была решением. В отличие от коллекции Queue, Deque позволяет перемещать предметы вперед / назад вперед и назад. Это похоже на связанный список, но с улучшенной производительностью.

Адам Кокс
источник
1
Повторяйте ваше утверждение, Dequeкоторое «похоже на связанный список, но с улучшенной производительностью» . Пожалуйста квалифицироваться это утверждение: Dequeлучше производительность , чем LinkedList, для конкретного кода . Перейдя по вашей ссылке, я вижу, что через два дня вы узнали от Ивана Стоева, что это не неэффективность LinkedList, а неэффективность вашего кода. (И даже если бы это был неэффективный LinkedList, это не оправдывало бы общее утверждение, что Deque более эффективен; только в определенных случаях.)
ToolmakerSteve