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

когда лучше использовать список vs a LinkedList?

15 ответов


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

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

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

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

// 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;

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




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

связанный список (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;

связанный список, имеющий ссылку на место, куда вставить (.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;

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


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

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

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


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

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

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

они имеют различные эксплуатационные характеристики тоже:

добавить

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

добавить

  • 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> предлагает линейное время, так как дополнительные элементы в списке должны быть перемешаны после вставки / удаления.

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

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

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


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

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

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


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


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


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

краткие итоги:

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

    • как можно чаще. Это быстро и занимает наименьший диапазон ОЗУ для той же информации.
    • если вы знаете точное количество клеток, необходимых
    • если данные сохранены в массиве
    • при необходимости высокая скорость произвольного доступа
  • список нужно использовать:

    • при необходимости добавить ячейки в конец списка (часто)
    • при необходимости добавить ячейки в начале / середине список (не часто)
    • если данные сохранены в массиве
    • при необходимости высокая скорость произвольного доступа
  • LinkedList нужно использовать:

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

Подробнее:

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

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

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

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

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


обычным обстоятельством для использования 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();
}

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


это взято из Тоно ДНПпринятый ответ исправляет несколько неправильных измерений в нем.

тест:

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> приобретает большое время в случае вставок. Я не тестировал удаление из середины списка, но результат должен быть таким же. Конечно!--3--> есть другие области, где он выполняет лучше, как O(1) произвольного доступа.


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

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

использовать LinkedList<>, когда

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

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


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

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

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

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

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


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


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

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

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

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