Когда лучше использовать List(Of T) vs a LinkedList(Of T)?
Когда мне следует использовать список против LinkedList
Ответ 1
Изменить
Прочтите комментарии к этому ответу. Люди утверждают, что я не делал надлежащие тесты. Я согласен, что это не должно быть приемлемым ответом. Как я был Я провела несколько тестов и почувствовала, как их разделять.
Оригинальный ответ...
Я нашел интересные результаты:
// 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;
Итак, только если вы планируете вставлять несколько элементов, а также, где-то есть ссылка, где вы планируете вставить элемент, а затем использовать связанный список. Просто потому, что вам нужно вставить множество элементов, это не ускоряет работу, поскольку поиск места, в который вы хотите вставить, требует времени.
Ответ 2
В большинстве случаев List<T> более полезен. LinkedList<T> будет иметь меньшую стоимость при добавлении/удалении элементов в середине списка, тогда как List<T> может только дешево добавить/удалить в конце списка.
 LinkedList<T> работает только в том случае, если вы получаете доступ к последовательным данным (либо вперед, либо назад) - случайный доступ является относительно дорогостоящим, так как он должен каждый раз ходить по цепочке (следовательно, у него нет индексатора). Однако, поскольку List<T> по существу является просто массивом (с оберткой), то произвольный доступ в порядке.
 List<T> также предлагает множество методов поддержки - Find, ToArray и т.д.; однако они также доступны для LinkedList<T> с .NET 3.5/С# 3.0 с помощью методов расширения - так что это менее важно.
Ответ 3
Мышление связанного списка в виде списка может немного ввести в заблуждение. Это больше похоже на цепочку. На самом деле, в .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)линейное время
Count
-  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> предлагает линейное время, так как дополнительные элементы в списке должны быть перемешаны после вставки/удаления.
Ответ 4
Связанные списки обеспечивают очень быструю вставку или удаление члена списка. Каждый член в связанном списке содержит указатель на следующий член в списке, чтобы вставить элемент в позицию i:
- обновить указатель в элементе i-1, чтобы указать на новый элемент
- установите указатель в новом члене, чтобы указать член i
Недостатком связанного списка является то, что случайный доступ невозможен. Доступ к члену требует прохождения списка до тех пор, пока не будет найден нужный элемент.
Ответ 5
Разница между списком и LinkedList заключается в их основной реализации. Список представляет собой массив на основе массива (ArrayList). LinkedList - это коллекция node -interinter based (LinkedListNode). При использовании уровня API оба они почти одинаковы, поскольку оба реализуют один и тот же набор интерфейсов, таких как ICollection, IEnumerable и т.д.
Ключевое различие возникает, когда производительность имеет значение. Например, если вы реализуете список с тяжелой операцией "INSERT", LinkedList превосходит List. Поскольку LinkedList может сделать это в O (1) раз, но List может потребоваться расширить размер базового массива. Для получения дополнительной информации/подробностей вам может потребоваться ознакомиться с алгоритмической разницей между LinkedList и структурами данных массива. http://en.wikipedia.org/wiki/Linked_list и Array
Надеюсь на эту помощь,
Ответ 6
Основным преимуществом связанных списков по массивам является то, что ссылки предоставляют нам возможность эффективно перестраивать элементы. Sedgewick, p. 91
Ответ 7
Мой предыдущий ответ был недостаточно точным. Как это было ужасно: D Но теперь я могу опубликовать гораздо более полезный и правильный ответ.
Я сделал несколько дополнительных тестов. Вы можете найти его источник по следующей ссылке и переустановить его в своей среде по своему усмотрению: https://github.com/ukushu/DataStructuresTestsAndOther.git
Короткие результаты:
-  Массив должен использовать: - Так часто, насколько это возможно. Он быстро и занимает наименьший объем оперативной памяти для информации о том же объеме.
- Если вы знаете точное количество необходимых ячеек
- Если данные, сохраненные в массиве < 85000 b
- Если требуется высокая скорость произвольного доступа
 
-  Список должен использовать: - Если необходимо добавить ячейки в конец списка (часто)
- Если необходимо добавить ячейки в начало/середину списка (NOT OFTEN)
- Если данные, сохраненные в массиве < 85000 b
- Если требуется высокая скорость произвольного доступа
 
-  LinkedList должен использовать: - Если необходимо добавить ячейки в начало/середину/конец списка (часто)
- При необходимости только последовательный доступ (вперед/назад)
- Если вам нужно сохранить LARGE-элементы, но количество элементов будет низким.
- Лучше не использовать для большого количества элементов, так как он использует дополнительную память для ссылок.
 
Подробнее:
-  Связанный список внутри не является списком в .NET. LinkedList<T>. Он даже не реализуетIList<T>. И поэтому отсутствуют индексы и методы, связанные с индексами.
-  LinkedList<T>представляет собой коллекцию node -interinter. В .NET это связано с двойной связью. Это означает, что предыдущие/следующие элементы имеют ссылку на текущий элемент. И данные фрагментированы - разные объекты списка могут быть расположены в разных местах ОЗУ. Кроме того, дляLinkedList<T>будет больше памяти, чем дляList<T>или массива.
-  List<T>в .Net является альтернативой JavaArraList<T>. Это означает, что это оболочка массива. Таким образом, он выделяется в памяти как один непрерывный блок данных. Если выделенный размер данных превышает 85000 байт, он будет выделен как часть большой кучи объектов. В зависимости от размера это может привести к фрагментации кучи, легкой утечке памяти. Но в то же время, если размер < 85000 байт - это обеспечивает очень компактное представление с быстрым доступом в памяти.
-  Единственный непрерывный блок предпочтительнее для производительности произвольного доступа и потребления памяти, но для коллекций, которые должны регулярно менять размер, структура, такая как массив, обычно должна быть скопирована в новое место, тогда как связанный список должен управлять только память для вновь вставленных/удаленных узлов. 
Ответ 8
Общим для использования LinkedList является следующее:
Предположим, вы хотите удалить многие строки из списка строк большого размера, скажем, 100 000. Строки для удаления можно найти в HashSet dic, и, как считается, список строк содержит от 30 000 до 60 000 таких строк для удаления.
Тогда какой лучший тип списка для хранения 100 000 строк? Ответ: LinkedList. Если они хранятся в ArrayList, то итерация по ней и удаление совпадающих строк, которые будут занимать к миллиардам операций, тогда как требуется около 100 000 операций с использованием итератора и метода remove().
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();
}
Ответ 9
Если вам нужен встроенный индексированный доступ, сортировка (и после этого двоичного поиска) и метод ToArray(), вы должны использовать List.
Ответ 10
Это адаптировано из Tono Nam, принявшего ответ, исправляющий несколько неправильных измерений в нем.
Тест:
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 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) произвольный доступ.
Ответ 11
Используйте LinkedList<>, когда
-  Вы не знаете, сколько объектов проходит через шлюз. Например, Token Stream.
- Когда вы ТОЛЬКО хотите удалить \insert в конце.
Для всего остального лучше использовать List<>.
Ответ 12
Здесь так много средних ответов...
В некоторых реализациях связанных списков используются базовые блоки предварительно выделенных узлов. Если они этого не делают, то постоянное время/линейное время менее важно, так как производительность памяти будет плохой, а производительность кэша еще хуже.
Используйте связанные списки, когда
1) Вам нужна безопасность потоков. Вы можете создавать улучшенные поточные альги. Затраты на блокировку будут доминировать в параллельном списке стилей.
2) Если у вас большая структура, похожая на очередь, и вы хотите удалить или добавить где угодно, кроме конца все время. > 100K-списки существуют, но не так распространены.
Ответ 13
Я спросил аналогичный вопрос, связанный с производительностью коллекции LinkedList, и обнаружил Стивен Клири Реализация С# для Deque была решением. В отличие от коллекции Queue, Deque позволяет перемещать элементы взад и вперед спереди и сзади. Он похож на связанный список, но с улучшенной производительностью.

