ChatGPT解决这个技术问题 Extra ChatGPT

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

MyClass[] array;
List<MyClass> list;

一种比另一种更可取的情况是什么?为什么?

数组相当过时,如此处的 popular discussion 所示。还有 pointed out here,我们的主机 in the blog
如果我没记错的话列表<>有一个数组作为内部结构。每当内部数组被填充时,它只需将内容复制到一个两倍大小的数组(或其他一些常数乘以当前大小)。 en.wikipedia.org/wiki/Dynamic_array
Ykok:你说的很对,我找到了List<> here的源代码。
@gimel 争论数组已经过时可能有点大胆

C
Community

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

List<T> 提供了比数组更多很多的功能(尽管 LINQ 稍微平衡了一点),而且几乎总是正确的选择。当然,除了 params 参数。 ;-p

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

也可以看看:

如何/何时放弃在 c#.net 中使用数组?

数组,有什么意义?

也就是说,我在我的 protobuf-net 项目中大量使用了数组;完全出于性能:

它做了很多位移,因此 byte[] 对于编码非常重要;

我使用本地滚动字节 [] 缓冲区,在发送到底层流(和 vv)之前填充该缓冲区;比 BufferedStream 等更快;

它在内部使用基于数组的对象模型(Foo[] 而不是 List),因为一旦构建,大小就固定了,并且需要非常快。

但这绝对是个例外;对于一般业务线处理,List<T> 每次都会获胜。


关于调整大小的论点是完全有效的。然而,即使不需要调整大小,人们也更喜欢列表。对于后一种情况,是否有一个可靠的、合乎逻辑的论点,还是仅仅是“数组过时了”?
“在您想要添加/删除数据的任何时候,一定要使用 List,因为调整数组大小很昂贵。” List 在内部使用数组。你在想 LinkedList 吗?
更多功能 == 更复杂 == 不好,除非您需要这些功能。这个答案基本上列出了数组更好的原因,但得出了相反的结论。
@EamonNerbonne,如果您不使用这些功能,我几乎可以保证它们不会伤害您……但是:根据我的经验,永远不需要突变的集合的数量比那些突变
@MarcGravell:这取决于您的编码风格。根据我的经验,几乎没有任何集合发生过变异。那是;集合是从数据库中检索或从某个来源构建的,但始终通过重新创建新集合(例如映射/过滤器等)来完成进一步的处理。即使在需要概念突变的情况下,生成一个新集合往往也是最简单的。我只对集合进行变异作为性能优化,并且这种优化往往是高度本地化的,不会将变异暴露给 API 使用者。
B
Brian

真的只是回答添加一个我很惊讶的链接还没有被提及:Eric's Lippert's blog entry on "Arrays considered somewhat harmful."

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


三年多后终于有时间读这个了哈哈。好文章,现在好文章。 :)
A
Alnitak

尽管其他答案推荐 List<T>,但您在处理时需要使用数组:

图像位图数据

其他低级数据结构(即网络协议)


为什么要使用网络协议?你不想在这里使用自定义结构并给它们一个特殊的序列化器或显式内存布局吗?此外,什么反对在这里使用 List<T> 而不是字节数组?
@Konrad - 好吧,对于初学者来说,Stream.Read 和 Stream.Write 与 byte[] 一起使用,Encoding 等也是如此......
S
Spencer Ruport

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


“你为什么使用 .Net 而不是 C++?” XNA
详细说明@Bengt 评论,性能不是.NET 的优先事项,这绝对是公平的。但是一些聪明的人想到了将 .NET 与游戏引擎一起使用。事实上,现在大多数游戏引擎都使用 C#。在这方面,Unity 3D 是“当游戏引擎不适合 AAA 时”的范例。
@mireazma 很长一段时间以来都不是有效的声明(wrt Unity)。我们通过 HPC# 和 Burst 从 C# 中获得 C++ 性能。许多引擎内部正在迁移到 C#。即使您谈论的是非 DOTS 项目中的游戏脚本,IL2CPP 在生成高性能代码方面也做得非常非常好。
@3Dave 我们不要就此展开争论。 “IL2CPP 在生成高性能代码方面做得非常非常好”。确实如此,在其影响范围内。与 jacksondunstan.com/articles/3001(2015 年)相反,我认为 IL2CPP 与单声道相比在性能方面做得非常好。但与单声道相比。与本机 C++ 相比,这是无稽之谈。我在 Unity 中使用 il2cpp 在 released exe 中进行了算术比较测试,所有优化与 C++ 相比,Unity 2020 中的时间分别为 18745、18487ms 和更多时间,而 C++ 中的时间为 189ms(有时约为 36ms )。自己做一个测试。
@mireazma 我的评论旨在更关注 Burst 和 HPC#,我们认为性能与等效的本机代码一样好(在某些情况下更好)。不过,您的基准测试数字很有趣。我会调查的。干杯。
H
Herman Schoenfeld

当集合本身的不变性是客户端和提供者代码之间合同的一部分(不一定是集合中项目的不变性)并且 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');

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


我对 C# 比较陌生,但我不清楚为什么返回列表会暗示原始数据的可变性,而返回数组则不会。我会认为名称以 To 开头的方法将创建一个无法修改原始实例的对象,而不是 strChars as char[],如果有效则表明您现在可以修改原始实例目的。
@TimMB 集合的不变性(无法添加或远程项目)和集合中项目的不变性。我指的是后者,而您可能将两者混为一谈。返回数组可确保客户端无法添加/删除项目。如果是这样,它会重新分配数组并确保它不会影响原始数组。返回一个列表,不做这样的保证,原始可能会受到影响(取决于实施)。如果项目的类型不是结构,则更改集合中的项目(无论是数组还是列表)可能会影响原始项目。
谢谢你的澄清。我仍然很困惑(可能是因为我来自 C++ 世界)。如果 str 在内部使用数组并且 ToCharArray 返回对该数组的引用,则客户端可以通过更改该数组的元素来改变 str,即使大小保持固定。然而你写了'很明显,修改“strChars”不会改变原始的“str”对象'。我在这里想念什么?从我所见,在任何一种情况下,客户端都可以访问内部表示,并且无论类型如何,这都会允许某种类型的突变。
s
smack0007

如果我确切地知道我需要多少个元素,比如说我需要 5 个元素并且只需要 5 个元素,那么我会使用一个数组。否则我只使用 List


在您知道元素数量的情况下,为什么不使用 List 呢?
S
Sune Rievers

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

有关 C# 中的列表的更多信息,请参阅 http://msdn.microsoft.com/en-us/library/ms379570(v=vs.80).aspx#datastructures20_1_topic5,或者只需反编译 System.Collections.Generic.List<T>

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

与往常一样,如果内存或性能是一个问题,请测量它!否则,您可能会对代码做出错误的假设。


嗨,你能解释一下为什么“一个列表的查找时间是 O(n)”是真的吗?据我所知 List 在幕后使用数组。
@dragonfly 你是完全正确的。 Source。当时,我假设实现使用了指针,但后来我学到了其他方法。从上面的链接:'检索此属性的值是 O(1) 操作;设置属性也是一个 O(1) 操作。
C
Christian Findlay

数组与。列表是一个经典的可维护性与性能问题。几乎所有开发人员都遵循的经验法则是,您应该同时兼顾两者,但是当它们发生冲突时,请选择可维护性而不是性能。该规则的例外情况是性能已经被证明是一个问题。如果你把这个原则带到 Arrays Vs.列表,那么你得到的是这样的:

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


这切入了我们必须面对的核心业务决策,时间就是金钱,与列表或对象集合相比,数组通常最终会花费更多的时间来开发和维护,尤其是多维数组。但在许多情况下,它们会以较小的性能和内存损失为代价
s
supercat

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

尽管数组不可调整大小,但不难让代码存储数组引用以及正在使用的元素数量,并根据需要用更大的数组替换数组。或者,可以轻松地为一种行为类似于 List<T> 但暴露其后备存储的类型编写代码,从而允许使用 MyPoints.Add(nextPoint);MyPoints.Items[23].X += 5;。请注意,如果代码尝试访问列表末尾之外的内容,后者不一定会引发异常,但在概念上的用法与 List<T> 非常相似。


你描述的是一个列表<>。有一个索引器,因此您可以直接访问底层数组,并且 List<> 将为您维护大小。
@Carl:给定例如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); 但不存在这样的功能。
好消息。有用。 list[0].X += 3; 将向列表第一个元素的 X 属性添加 3。 listList<Point>Point 是具有 X 和 Y 属性的类
m
moarboilerplate

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

大多数情况下,在托管代码中,您希望集合尽可能易于使用,而不是担心微优化。


i
iliketocode

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


s
snipsnipsnip

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

因此,如果您正在编写一个接受 List<MyClass> 作为参数但仅使用部分功能的方法,则为了方便调用者,您可能希望声明为 IList<MyClass>

细节:

为什么数组实现IList?

C# 中的数组如何部分实现 IList


“在 C# 中,数组就是一个列表” 这不是真的;数组不是 List,它只实现 IList 接口。
佚名

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

话虽如此,在实践中,我使用 List 的次数远远多于 Arrays,但它们各有各的位置。

如果 List 有一个内置类型,那就太好了,这样他们就可以优化包装器和枚举开销。


B
Bimal Poudel

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

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();

A
Alberto Costa

请记住,使用 List 无法做到这一点:

List<string> arr = new List<string>();

arr.Add("string a");
arr.Add("string b");
arr.Add("string c");
arr.Add("string d");

arr[10] = "new string";

它会生成一个异常。

而是使用数组:

string[] strArr = new string[20];

strArr[0] = "string a";
strArr[1] = "string b";
strArr[2] = "string c";
strArr[3] = "string d";

strArr[10] = "new string";

但是对于数组,没有自动调整数据结构的大小。您必须手动或使用 Array.Resize 方法来管理它。

一个技巧可能是用一个空数组初始化一个 List。

List<string> arr = new List<string>(new string[100]);

arr[10] = "new string";

但是在这种情况下,如果您使用 Add 方法放置一个新元素,它将被注入到 List 的末尾。

List<string> arr = new List<string>(new string[100]);

arr[10] = "new string";

arr.Add("bla bla bla"); // this will be in the end of List

我不确定这是否会给讨论增加任何内容。它们是真实的陈述,但措辞不是为了帮助您选择一个而不是另一个。它只强调了对 new List<T>(int) 构造函数的误解。
我同意你的看法,将列表用作数组是不正确的。也许我可以更好地解释这个概念。列表很方便并且在程序员没有注意到的情况下增长(魔术?),并且您只能在最后添加项目。另一方面,数组不会自动缩放,程序员必须通过创建一个新的更大的数组并复制旧数组的元素来完成。数组比列表更有效地占用内存。
这是一篇旧帖子,接受的答案简洁地涵盖了这种情况。如果您要重新打开帖子进行讨论,那么请提出一些新内容或给我们一个真正的理由来投票支持您的答案,您应该直接评论为什么您的答案更好。您已经添加了一些有关所讨论类型的入门级信息,但没有评论人们可能会通过哪些方法来选择在其代码中使用哪些类型。
s
sajidnizami

它完全取决于需要数据结构的上下文。例如,如果您正在创建项目以供其他功能或服务使用,那么使用 List 是完成它的完美方式。

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


如果您有一个项目列表并且只想显示它们,那么仅使用已有的列表有什么问题?数组会在这里提供什么?
而对于“创建供其他功能或服务使用的项目”,实际上,我更喜欢带有 IEnumerable<T> 的迭代器块 - 然后我可以流式传输对象而不是缓冲它们。