当前位置:  开发笔记 > 编程语言 > 正文

我什么时候应该使用List vs LinkedList

如何解决《我什么时候应该使用ListvsLinkedList》经验,为你挑选了7个好方法。

什么时候使用List vs LinkedList更好?



1> Marc Gravell..:

在大多数情况下,List更有用.LinkedList在列表中间添加/删除项目时会有更少的成本,而List只能在列表末尾便宜地添加/删除.

LinkedList如果您正在访问顺序数据(向前或向后),那么它的效率最高 - 随机访问相对昂贵,因为它每次都必须走链(因此它没有索引器).但是,因为a List本质上只是一个数组(带有包装器),随机访问就可以了.

List还提供了很多的支持方法- Find,ToArray等; 但是,这些也可以LinkedList通过扩展方法用于.NET 3.5/C#3.0 - 因此这不是一个因素.


List <> vs. LinkedList <>的一个优点是我从未想过要关注微处理器如何实现内存缓存.虽然我不完全理解它,但是这篇博客文章的作者谈论了很多关于"引用的位置"的内容,这使得遍历数组**比遍历链表更快**,至少如果链表已成为在记忆中有点碎片.http://kjellkod.wordpress.com/2012/02/25/why-you-should-never-ever-ever-use-linked-list-in-your-code-again/
由于List是一个动态数组,因此如果事先知道List的容量,那么有时最好在构造函数中指定List的容量。

2> Drew Noakes..:

将链表视为列表可能会有点误导.它更像是一个链条.实际上,在.NET中,LinkedList甚至都没有实现IList.链表中没有真正的索引概念,即使它看起来似乎存在.当然,类中提供的方法都不接受索引.

链接列表可以单独链接,也可以双重链接.这指的是链中的每个元素是否仅与下一个元素(单链接)或前一个/下一个元素(双重链接)都有链接. LinkedList是双重联系的.

在内部,List由数组支持.这在内存中提供了非常紧凑的表示.相反,LinkedList涉及额外的存储器以存储连续元素之间的双向链接.因此,a的内存占用量LinkedList通常会大于List(因为List可以使用未使用的内部数组元素来提高追加操作期间的性能.)

它们也有不同的性能特征:

附加

LinkedList.AddLast(item) 恒定时间

List.Add(item) 摊销常数时间,线性最坏情况

前置

LinkedList.AddFirst(item) 恒定时间

List.Insert(0, item) 线性时间

插入

LinkedList.AddBefore(node, item) 恒定时间

LinkedList.AddAfter(node, item) 恒定时间

List.Insert(index, item) 线性时间

切除

LinkedList.Remove(item) 线性时间

LinkedList.Remove(node) 恒定时间

List.Remove(item) 线性时间

List.RemoveAt(index) 线性时间

计数

LinkedList.Count 恒定时间

List.Count 恒定时间

包含

LinkedList.Contains(item) 线性时间

List.Contains(item) 线性时间

明确

LinkedList.Clear() 线性时间

List.Clear() 线性时间

如你所见,它们大部分都是等价的.在实践中,API的LinkedList使用更加繁琐,其内部需求的细节也会泄露到您的代码中.

但是,如果您需要在列表中执行多次插入/删除操作,则会提供恒定时间. List提供线性时间,因为列表中的额外项目必须在插入/移除后随机移动.


@Iain,计数缓存在两个列表类中.
你写了"List .Add(item)对数时间",但是如果列表容量可以存储新项目它实际上是"常量",如果列表没有足够的空间而且它是新的"线性"要重新分配.
计数链表是否常数?我以为这是线性的?

3> b3...:

链接列表可以非常快速地插入或删除列表成员.链表中的每个成员都包含指向列表中下一个成员的指针,以便在位置i处插入成员:

更新成员i-1中的指针以指向新成员

将指针放在新成员中以指向成员i

链表的缺点是随机访问是不可能的.访问成员需要遍历列表,直到找到所需的成员.


是的,连续块对于随机访问性能和内存消耗是首选,但对于需要定期更改大小的集合,通常需要将诸如Array之类的结构复制到新位置,而链表仅需要管理内存以用于新插入/删除的节点.
我想补充一点,链接列表通过LinkedListNode隐藏了上面隐含的每个项目的开销,LinkedListNode引用了上一个和下一个节点.与基于数组的列表不同,存储列表不需要连续的内存块.
如果您曾经不得不使用非常大的数组或列表(列表只是包装数组),即使您的计算机上有足够的可用内存,您也会开始遇到内存问题.该列表在其底层数组中分配新空间时使用加倍策略.因此,一个1000000元素的elemnt数组将被复制到一个包含2000000个元素的新数组中.这个新数组需要在一个足够大的连续内存空间中创建,以容纳它.
通常不是连续的内存块吗?

4> Tono Nam..:

编辑

请阅读此答案的评论.人们声称我没有做适当的测试.我同意这不应该是一个公认的答案.在我学习的过程中,我做了一些测试,感觉就像分享它们一样.

原始答案......

我发现了有趣的结果:

// 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 list = new LinkedList();

        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 list = new List(); // 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 list = new LinkedList();

        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 list = new List();

        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;

因此,只有当您计划插入多个项目时,您可以在某个地方引用计划插入项目的位置,然后使用链接列表.仅仅因为你必须插入很多项目它不会使它更快,因为搜索你想要插入它的位置需要时间.


LinkedList over List有一个好处(这是.net特定的):由于List由内部数组支持,因此它被分配在一个连续的块中.如果该分配的块大小超过85000字节,则它将在大对象堆上分配,这是一个不可压缩的生成.根据大小,这可能导致堆碎片,一种温和的内存泄漏形式.
对不起,但**这个答案非常糟糕.请不要听这个答案.**原因简而言之:认为阵列支持的列表实现足以在每次插入时调整数组大小是完全错误的.当遍历以及在任一端插入时,链接列表自然比阵列支持列表慢,因为只有它们需要创建新对象,而阵列支持列表使用缓冲区(显然在两个方向上).(做得不好)基准测试表明了这一点.答案完全没有检查链接列表更可取的情况!
请注意,如果您在前面做了很多(正如您在上一个示例中所做的那样)或删除第一个条目,则链表几乎总是要快得多,因为没有搜索或移动/复制要做.列表需要将所有内容移动到一个位置以容纳新项目,从而预先进行O(N)操作.
我贬低了这个答案.1)你的一般建议`我说永远不会使用linkList.有缺陷,因为你后来的帖子揭示了.您可能想要编辑它.2)你有什么时间安排?实例化,添加和枚举一步一步完成?大多数情况下,实例化和枚举并不是人们所担心的,这些都是一次性步骤.具体来说,插入和添加的时间会给出更好的想法.3)最重要的是,您添加的链接列表超出了要求.这是一个错误的比较.传播关于链表的错误想法.
为什么在最后两个LinkedList示例中使用in循环`list.AddLast(a);`?我在循环之前做了一次,就像`list.AddLast(new Temp(1,1,1,1));`在最后一个LinkedList的下一个,但它看起来(对我来说)就像你要添加两次一样循环中的尽可能多的Temp对象.(当我[用测试应用程序仔细检查自己](http://pastebin.com/i440C312)时,肯定是在LinkedList中的两倍.)
注意:这听起来完全是任何链表实现的典型,而不仅仅是.Net的.
我认为值得注意的是List是使用数组实现的.这意味着一旦列表超出初始大小,就需要扩展此数组.这也是O(n)操作.与链表相比,不需要存储,因此我们永远不会有昂贵的操作O(n).在列表的开头附加大量数据时,Alinked列表非常方便.如我错了请纠正我.
这是一个完全不了解的答案.它不应该是选择的答案或最佳结果!
@Quonux严格地说,是的 - 但是一个支离破碎的LOH几乎与内存泄漏相同; 分配的内存将继续增长.
我很惊讶地发现所选答案是从计算机科学的角度完全忽略了List / Array vs LinkedList的实现的答案。遍历链接列表会使速度变慢!

5> Andrew..:

我之前的回答不够准确.真的很可怕:D但现在我可以发布更有用和正确的答案.


我做了一些额外的测试.您可以通过以下链接找到它的来源,并通过您自己的环境重新检查它:https://github.com/ukushu/DataStructuresTestsAndOther.git

结果短:

数组需要使用:

经常这样.它速度快,占用的RAM范围最小,可用于相同数量的信息.

如果你知道所需细胞的确切数量

如果数据保存在数组<85000 b

如果需要高随机访问速度

列表需要使用:

如果需要将单元格添加到列表末尾(通常)

如果需要在列表的开头/中间添加单元格(NOT OFTEN)

如果数据保存在数组<85000 b

如果需要高随机访问速度

LinkedList需要使用:

如果需要在列表的开头/中间/末尾添加单元格(通常)

如果只需要顺序访问(前进/后退)

如果您需要保存大量物品,但物品数量很少.

最好不要使用大量的项目,因为它使用额外的内存链接.

更多细节:

введитесюдаописаниеизображения 有趣的是:

    LinkedListinternal不是.NET中的List.它甚至没有实现IList.这就是为什么缺少与索引相关的索引和方法的原因.

    LinkedList是基于节点指针的集合.在.NET中,它是双重链接的实现.这意味着先前/下一个元素具有到当前元素的链接.并且数据是碎片化的 - 不同的列表对象可以位于RAM的不同位置.此外,将使用LinkedList比用于List或数组更多的内存.

    List在.Net是Java的替代品ArrayList.这意味着这是数组包装器.因此它在内存中被分配为一个连续的数据块.如果分配的数据大小超过85000字节,它将被移动到大对象堆.根据大小,这可能导致堆碎片(一种温和的内存泄漏形式).但是在同一时间,如果大小<85000字节 - 这在内存中提供了非常紧凑和快速访问的表示.

    单个连续块对于随机访问性能和内存消耗是首选,但对于需要定期更改大小的集合,通常需要将诸如Array的结构复制到新位置,而链表仅需要管理新插入的内存/删除节点.



6> 小智..:

List和LinkedList之间的区别在于它们的底层实现.List是基于数组的集合(ArrayList).LinkedList是基于节点指针的集合(LinkedListNode).在API级别的使用上,它们都非常相同,因为它们都实现了相同的接口集,例如ICollection,IEnumerable等.

当性能很重要时,关键的区别就在于 例如,如果要实现具有大量"INSERT"操作的列表,则LinkedList优于List.由于LinkedList可以在O(1)时间内完成,但List可能需要扩展底层数组的大小.有关更多信息/详细信息,您可能需要了解LinkedList和数组数据结构之间的算法差异.http://en.wikipedia.org/wiki/Linked_list和Array

希望这有帮助,


List 是基于数组(T []),而不是基于ArrayList.重新插入:数组调整大小不是问题(加倍算法意味着大部分时间它不必这样做):问题是它必须先阻塞复制所有现有数据,这需要一点点时间.
@Marc,'倍增算法'只能使它成为O(logN),但它仍然比O(1)差
我的观点是,这不是导致疼痛的调整大小 - 它是一种愚蠢的行为.最糟糕的情况是,如果我们每次都添加第一个(第0个)元素,那么blit每次都必须移动所有内容.

7> 小智..:

链接列表相对于数组的主要优点是链接为我们提供了有效重新排列项目的功能.塞奇威克,p.91

推荐阅读
郑谊099_448
这个屌丝很懒,什么也没留下!
DevBox开发工具箱 | 专业的在线开发工具网站    京公网安备 11010802040832号  |  京ICP备19059560号-6
Copyright © 1998 - 2020 DevBox.CN. All Rights Reserved devBox.cn 开发工具箱 版权所有