数组与List <T>:何时使用哪个?


592
MyClass[] array;
List<MyClass> list;

一个比另一个更可取的情况是什么?又为什么呢


9
如此处的热门讨论所示,数组已经过时了。这里指出了,并由我们的主持人在博客中
09年

9
如果我没记错的话,List <>具有一个数组作为内部结构。每当填充内部数组时,只需将内容复制到两倍大小的数组(或其他常数乘以当前大小)。en.wikipedia.org/wiki/Dynamic_array
Ykok

Ykok:您所说的似乎正确,我在这里找到了List <>的源代码。
卡罗尔2014年

19
@gimel争论数组已过时也许有点大胆
awdz9nld15年

Answers:


580

实际上,很少要使用数组。绝对List<T>要在任何时候添加/删除数据,因为调整数组的大小很昂贵。如果您知道数据是固定长度的,并且出于某些非常特定的原因(基准化之后)想要进行微优化,则数组可能会很有用。

List<T>提供一个很多比数组更多的功能(尽管LINQ找齐它一点),而且几乎总是正确的选择。params当然,除了争论。;-p

作为计数器- List<T>是一维的;where-as具有矩形int[,]或类似的数组,例如string[,,]- 或-,但是还有其他方法可以在对象模型中对此类数据进行建模(如果需要)。

也可以看看:

也就是说,我赚了很多protobuf-net项目中使用了数组;完全出于性能考虑:

  • 它做了很多位移,所以a byte[]对于编码非常重要;
  • 我使用本地滚动 byte[]缓冲区,在向下发送到基础流(和vv)之前先填充该缓冲区;比BufferedStream等更快
  • 它在内部使用基于数组的对象模型(Foo[]而不是List<Foo>),因为大小一旦建立便是固定的,并且需要非常快。

但这绝对是一个例外。对于一般的业务流程处理,List<T>每次都会取胜。


8
关于调整大小的论点是完全正确的。但是,即使不需要调整大小,人们还是更喜欢列表。对于后一种情况,是否存在一个可靠的逻辑论证,或者仅仅是“数组已过时”?
弗雷德里克的傻瓜

6
“由于要调整数组的大小很昂贵,因此您随时可以添加/删除数据时绝对使用List <T>。” List <T>在内部使用数组。您是否想过LinkedList <T>?
dan-gph 2010年

14
更多功能==更复杂==不好,除非您需要这些功能。该答案主要列出了数组更好的原因,但得出相反的结论。
Eamon Nerbonne 2014年

12
@EamonNerbonne,如果您不使用这些功能,我可以保证它们不会伤害您……但是:根据我的经验,不需要突变的集合的数量要比那些不需要的集合少得多。变异
马克·格雷夫

7
@MarcGravell:这取决于您的编码样式。以我的经验,几乎没有任何收藏品会发生变异。那是; 集合可以从数据库中检索或从某些来源构造,但是始终通过重新创建新集合(例如,地图/过滤器等)来进行进一步处理。即使需要进行概念上的更改,仅生成一个新集合也往往是最简单的。我只将集合作为性能优化进行了变异,而这种优化往往是高度本地化的,并且不会将变异暴露给API使用者。
Eamon Nerbonne 2014年

121

真正回答只是添加一个令我惊讶的链接还没有被提及:Eric的Lippert的博客条目 “数组被认为有些有害”。

您可以从标题中判断出它建议在可行的地方使用集合-但正如Marc正确指出的那样,在很多地方,数组实际上是唯一可行的解​​决方案。


2
终于经过三年多的时间阅读了这篇文章哈哈。那么好文章,现在好文章。:)
Spencer Ruport

21

尽管有其他建议List<T>,但在处理以下内容时仍要使用数组:

  • 图像位图数据
  • 其他低层数据结构(即网络协议)

1
为什么要使用网络协议?您是否愿意在这里使用自定义结构并为它们提供特殊的序列化程序或显式的内存布局?此外,什么反对使用List<T>here而不是字节数组呢?
康拉德·鲁道夫2009年

8
@Konrad-好吧,对于初学者来说,Stream.Read和Stream.Write可以和byte []一起工作,编码也一样……
Marc Gravell

12

除非您真的关心性能,否则我的意思是,“为什么要使用.Net而不是C ++?” 您应该坚持使用List <>。它更易于维护,并为您完成了在后台调整数组大小的所有繁琐工作。(如果需要,List <>对于选择数组大小非常聪明,因此通常不需要这样做。)


15
“为什么要使用.Net而不是C ++?” XNA
Bengt

6

数组应该当集合本身的不可变性是客户端与提供者代码之间的合同的一部分(不一定是集合中项目的不可变性)并且IEnumerable不适合时,优先于List使用。

例如,

var str = "This is a string";
var strChars = str.ToCharArray();  // returns array

显然,对“ strChars”的修改不会改变原始的“ str”对象,而与“ str”的基础类型的实现级别知识无关。

但是假设

var str = "This is a string";
var strChars = str.ToCharList();  // returns List<char>
strChars.Insert(0, 'X');

在这种情况下,仅凭该代码片段还不清楚insert方法是否会改变原始的“ str”对象。它需要String的实现级别的知识才能进行确定,这破坏了“按合同设计”方法。对于String而言,这没什么大不了的,但是在几乎所有其他情况下,这可能都是大问题。将列表设置为只读确实有帮助,但会导致运行时错误,而不是编译时错误。


我是C#的新手,但我不清楚为什么返回列表会以某种方式暗示原始数据的可变性,而不是返回数组。我会以其名字开头的方法To创建一个无法修改原始实例的对象,strChars as char[]而如果该方法有效,则表明您现在可以修改原始对象。
蒂姆·MB

@TimMB集合具有不变性(无法添加或远程项目),集合中具有不变性。我指的是后者,而您可能会将两者混为一谈。返回数组可确保客户端无法添加/删除项目。如果是这样,它将重新分配数组,并确保它不会影响原始数组。返回列表时,不会做出任何保证,原始保证可能会受到影响(取决于实施情况)。如果项的类型不是结构,则更改集合中的项(无论是数组还是列表)都可能影响原始项。
Herman Schoenfeld

谢谢你的澄清。我仍然很困惑(可能是因为我来自C ++世界)。如果str内部使用一个数组并ToCharArray返回对该数组的引用,则str即使大小保持固定,客户端也可以通过更改该数组的元素来进行更改。但是,您写道“很明显,对“ strChars”的修改不会使原始的“ str”对象发生变异”。我在这里想念什么?从我看到的情况来看,无论哪种情况,客户端都可以访问内部表示,并且无论类型如何,这都将允许某种形式的突变。
Tim MB

3

如果我知道我到底有多少元素去需求,说我需要5元,仅 5元,然后我使用一个数组。否则,我只使用List <T>。


1
如果您知道元素数,为什么不使用List <T>?
奥利弗·

3

在大多数情况下,使用a List就足够了。A List使用内部数组来处理其数据,并在List向其添加比当前容量更多的元素时自动调整数组的大小,这使其比需要事先了解容量的数组更易于使用。

有关C#中的列表或反编译的更多信息,请参见http://msdn.microsoft.com/zh-cn/library/ms379570(v=vs.80).aspx#datastructures20_1_topic5System.Collections.Generic.List<T>

如果您需要多维数据(例如,使用矩阵或在图形编程中使用),则可能需要使用 array改用。

与往常一样,如果内存或性能成为问题,请对其进行衡量!否则,您可能会对代码做出错误的假设。


1
嗨,您能解释一下为什么“列表的查找时间为O(n)”吗?据我所知,List <T>在后台使用数组。
蜻蜓2012年

1
@dragonfly你完全正确。来源。当时,我假设该实现使用了指针,但是从那以后我就学到了其他方法。从上面的链接:'检索此属性的值是O(1)操作;设置属性也是O(1)操作。”
苏恩·里维尔斯

2

阵列与 列表是经典的可维护性与性能问题。几乎所有开发人员都遵循的经验法则是,您应该同时为两者射击,但是当他们发生冲突时,请选择可维护性而不是性能。该规则的例外是当性能已被证明是一个问题时。如果您将此原则贯彻到Arrays Vs中。列表,那么您得到的是:

使用强类型列表,直到遇到性能问题。如果遇到性能问题,请决定是否退出阵列是否会使解决方案的性能受益,而不是对维护产生不利影响。


1

尚未提及的另一种情况是,当一个项目具有大量项目时,每个项目都由固定在一起的一束相关但独立的变量组成(例如,一个点的坐标或3d三角形的顶点)。暴露场结构的数组将允许其元素“就地”有效地修改-这是任何其他集合类型都不可能实现的。由于结构的数组将其元素连续保存在RAM中,因此对数组元素的顺序访问会非常快。在代码需要多次顺序通过数组的情况下,结构数组的性能可能比数组或类对象引用的其他集合的性能高2:1;进一步,

尽管数组不可调整大小,但是让代码存储数组引用以及正在使用的元素数量并不困难,并根据需要用更大的数组替换数组。另外,一个人可以很容易地为某种类型的代码编写代码,该代码的行为很像a,List<T>但是暴露了其后备存储,因此可以说出MyPoints.Add(nextPoint);or MyPoints.Items[23].X += 5;。请注意,如果代码尝试访问列表末尾以外的内容,则后者不一定会引发异常,但在概念上,用法与十分相似List<T>


您描述的是一个List <>。有一个索引器,因此您可以直接访问基础数组,并且List <>将为您保留大小。
卡尔

@Carl:给定eg Point[] arr;,代码可能会说arr[3].x+=q;。使用例如List<Point> list,有必要改为说Point temp=list[3]; temp.x+=q; list[3]=temp;。如果List<T>有一种方法将是有帮助的Update<TP>(int index, ActionByRefRef<T,TP> proc, ref TP params)。并且编译器可能会list[3].x+=q;变成这样,{list.Update(3, (ref int value, ref int param)=>value+=param, ref q);但是不存在这样的功能。
supercat

好消息。有用。list[0].X += 3;将3添加到列表的第一个元素的X属性。和listList<Point>Point是具有X和Y属性的类
卡尔

1

.NET中的列表是数组的包装器,并在内部使用数组。列表上操作的时间复杂度与数组上的时间复杂度相同,但是所有添加的功能/列表的易用性(例如自动调整大小和列表类随附的方法)会增加一些开销。几乎,我会建议在所有情况下都使用列表,除非有充分的理由这样做,例如,如果您需要编写经过优化的代码,或者正在使用围绕数组构建的其他代码。


0

我认为,与其比较每种数据类型的功能,不如说最实用的答案是:“差异对于您需要完成的工作可能并不那么重要,尤其是因为它们都是实现的IEnumerable,因此请遵循流行的约定并使用List直到您有理由不这样做,这时您可能才有理由在上使用数组List。”

大多数时候,在托管代码中,您会希望集合尽可能易于使用,而不必担心微优化。


0

他们可能不受欢迎,但我是游戏项目中Array的粉丝。-迭代速度在某些情况下可能很重要,如果您不对每个元素进行太多操作,则数组上的foreach开销会大大减少-使用辅助函数添加和删除并不困难-速度较慢,但​​是在仅构建一次的情况下没关系-在大多数情况下,浪费的额外内存更少(仅对结构数组有效)-更少的垃圾以及指针和指针追逐

话虽如此,实际上我比数组多使用List,但是它们每个都有自己的位置。

如果List在其中是内置类型,那将是很好的,这样它们可以优化包装程序和枚举开销。


0

填充列表比数组容易。对于数组,您需要知道数据的确切长度,但是对于列表,数据大小可以是任意大小。并且,您可以将列表转换为数组。

List<URLDTO> urls = new List<URLDTO>();

urls.Add(new URLDTO() {
    key = "wiki",
    url = "https://...",
});

urls.Add(new URLDTO()
{
    key = "url",
    url = "http://...",
});

urls.Add(new URLDTO()
{
    key = "dir",
    url = "https://...",
});

// convert a list into an array: URLDTO[]
return urls.ToArray();

0

由于没有人提及:在C#中,数组是一个列表。MyClass[]并且List<MyClass>都实现了IList<MyClass>。(例如,void Foo(IList<int> foo)可以称为Foo(new[] { 1, 2, 3 })Foo(new List<int> { 1, 2, 3 })

因此,如果编写的方法接受a List<MyClass>作为参数,但仅使用功能的子集,则可能需要声明为IList<MyClass>以方便调用者。

细节:


“在C#中,数组是一个列表”。数组不是List,它仅实现IList接口。
Rufus L

-1

它完全取决于需要数据结构的上下文。例如,如果您要使用List创建要由其他功能或服务使用的项目,则是实现此目标的理想方法。

现在,如果您有一个项目列表,而只想显示它们,则在网页数组上说您是需要使用的容器。


1
如果您有一个项目列表,而只想显示它们,那么仅使用现有列表有什么问题呢?阵列将在这里提供什么?
马克·格雷夫

1
对于“创建供其他功能或服务使用的项目”,实际上,我更喜欢使用带有IEnumerable<T>- 的迭代器块,然后我可以流式传输对象而不是对其进行缓冲。
马克·格雷韦尔

-1

值得一提的是当场铸造的能力。

interface IWork { }
class Foo : IWork { }

void Test( )
{
    List<Foo> bb = new List<Foo>( );
    // Error: CS0029 Cannot implicitly convert type 'System.Collections.Generic.List<Foo>' to 'System.Collections.Generic.List<IWork>'
    List<IWork> cc = bb; 

    Foo[] bbb = new Foo[4];
    // Fine
    IWork[] ccc = bbb;
}

因此,在函数的返回类型或参数中使用Array时,它提供了更多的灵活性。

IWork[] GetAllWorks( )
{
    List<Foo> fooWorks = new List<Foo>( );
    return fooWorks.ToArray( ); // Fine
}

void ExecuteWorks( IWork[] works ) { } // Also accept Foo[]

您应注意,数组的方差 损坏
mcarton
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.