列出<t>或LinkedList <t> </t> </t>

时间:2009-03-02 21:59:46

标签: .net algorithm data-structures collections

我需要一个包含相同类型元素列表的数据结构。需要的功能是

  • 添加
  • 的GetEnumerator
  • (可能)清除

索引访问,排序,搜索,删除元素不是必需的。什么是最好的收藏类?应考虑以下几个方面:性能,内存使用情况,垃圾收集器的行为。

我目前的候选人是List<T>LinkedList<T>

8 个答案:

答案 0 :(得分:23)

除非你正在处理一个庞大的结构,否则你计划迭代这个东西一万亿次,这没关系。只需选择一个并开始编码。如果您的应用程序稍后会慢慢爬行,请找出原因然后根据需要进行更改。

(说真的,确实如此。没有。问题。最轻微的。你花在寻找这个问题的答案上的每一分钟都离你很近,你可能已经有了工作代码了。)

如果有人已经达到了需要来了解差异的地步,则LinkedList比List更快,如果您只需要非随机,仅向前阅读和附加功能,则可以使用它。

答案 1 :(得分:8)

简短回答
默认情况下,几乎在所有情况下都使用List<T>

稍长的回答
LinkedList<T>只会在枚举这些值并且列表大小很大的情况下进行大量添加和删除时才会更好。如果您在使用List<T>进行分析后发现问题,那么这应该只是您选择的一个因素。

更长的答案

假设您已将某个或另一个的使用视为性能问题。

你做了很多随机访问,然后List<T>无论如何都几乎总是快得多。 如果你进行了很多枚举并且很少插入(或者几乎总是在末尾附近插入),List<T>几乎总是会更快。 如果你经常在随机位置插入/删除但是在遍历列表时已经在相关节点处或附近,并且至少有几千个元素你可能想要尝试LinkedList<T>

确定哪些值/用量转换为更好的性能在很大程度上取决于您的使用情况。 Microbenchmarks在这里可能会产生误导,因为它们会在链接列表行为的地毯方面进行刷新,就像在内存中分布的节点一样,如果它们碰巧在测试中一次性分配,则很好地相邻。同样,预先创建具有正确尺寸的List<T>可以产生很大的不同。

关于计算机科学风格推理和大O符号(在这种情况下,真的需要大N才有意义)

  • 操作
    • 费用List<T>
    • 费用LinkedList<T>
  • 插入到最后
    • O(1)(摊销成本,根据需要分配到双倍大小)
    • 每次O(1)分配
  • 在开始时插入
    • O(N)(尽管快速内存移动,因此运行时行为有点复杂)
    • O(1)
  • 插入位置x(并删除)
    • O(N-x)(见最后插入的评论)
    • O(1)
  • 前向枚举
    • O(N)(虽然缓存未命中最小化)
    • O(N)(虽然严重依赖于缓存局部性)
  • 反向枚举
    • O(N)
    • O(N)(LinkedList<T>实施是双重联系的)
  • 随机访问
    • O(1)
    • O(N)

内存使用情况很复杂,因为List在任何时候都可以拥有最多Count-1个多余的单元格,但LinkedList<T>将为每个单元格消耗LinkedListNode<T>,这是另外3个引用(4/8字节a pop)加上通常的对象开销。在正常使用情况下,List可能会赢,但如果您发现内存消耗实际上是一个问题,那么这应该只是您担心的事情。

答案 2 :(得分:7)

我会使用List<T>,因为如果它们是值类型并且仍然可以很好地处理引用类型,那么所有数据都会按顺序存储(在内部,List<T>管理一个因子而增长的数组每次空间用完时两个。)

当事情不是IO绑定时,

LinkedList<T>曾经更有意义。人们经常会引用其看似“O(1)”的性质。但是,这会降低页面错误获取节点的可能性带来的真实成本。

如果您可以使用数组或List<T>获得连续的内存区域并避免页面错误的可能性,那么使用现代处理器和主内存缓存线会更好。

如果您事先了解了多少元素,请使用数组。如果你很清楚有多少元素,请使用List<T>(并在构造函数中传递可能的上限以避免重新分配)。

我唯一一次使用LinkedList<T>的情况是,您需要不断地将列表中的项目按一个值进行处理。例如,如果您正在实施least recently used缓存算法,并且需要在前面添加一些内容并使用一些东西。

对于小件物品,它确实不会有所作为。分代垃圾收集器会随着时间的推移将分散的堆项压缩在一起,因此链表不会太糟糕。

除非你注意到问题(通过分析),否则我会选择List<T>并继续运行

答案 3 :(得分:7)

除非您正在处理数十万或数百万条记录,并且您已经分析了您的程序以确定存在重大问题,否则您可能不会注意到两者之间的区别。

除此之外:

  

LinkedList<T>提供类型LinkedListNode<T>的单独节点,因此插入和删除是O(1)操作。

来自here

答案 4 :(得分:3)

如果不需要索引访问,那么使用LinkedList<T>可能会更好。没有太大的区别,但LinkedList对于追加可以更快。

答案 5 :(得分:2)

鉴于您对界面的要求,您应该在代码中的任何地方使用ICollection<T>,而不是引用特定的具体类。

原因是如果您使用List,那么您可以编写一堆使用l[0]来获取第一项的代码。另一方面,如果您使用LinkedList,则可能会在整个代码中将调用传播到AddLast。要保留切换选项,您需要避免意外地依赖于您不需要的功能。您需要使用两个容器都可以支持的接口。

所以写一个这样的类:

public static class Collections
{
    public static ICollection<T> Make<T>()
    {
        return new List<T>();
    }
}

然后,无论何时需要集合,请执行以下操作:

ICollection<int> c = Collections.Make<int>();

将您选择的容器与程序的其余部分隔离开来。现在,当您分析并改变主意时,您只需编辑Make<T>方法。

答案 6 :(得分:0)

LinkedList将承载更多的内存开销(用于节点),但如果要在中间插入许多元素则更好。链表也需要更多的内存分配,因为LinkedListNode是一个类并且是动态分配的。如果你没有插入元素(只是追加),我会使用一个List。列表应该尽可能快或更快地添加,尽管它偶尔会扩大。

答案 7 :(得分:0)

如果您的阵列足够重要,那么您需要自己滚动...否则只需使用List。