列表可用时,是否有理由使用数组?[关闭]


30

List<T>在C#中,似乎可以完成数组可以做的所有事情,并且还可以完成更多工作,并且在内存和性能上也与数组一样高效。

那么,为什么我要使用数组呢?

我显然不是在问API或其他外部约束(即Main函数)需要我使用数组的情况……我只是在问关于在自己的代码中创建新数据结构的问题。


56
实施List<T>
棘轮怪胎2014年

43
is also just as efficient in memory and performance as an array嗯 你从哪里得到这个想法的?
奥德2014年

12
多个维度,如var test = new string[5,5];)
Knerd 2014年

7
还有常用的byte []数组。
SBoss 2014年

11
专门针对C#的Eric Lippert在blogs.msdn.com/b/ericlippert/archive/2008/09/22/…中写道。
Mephy 2014年

Answers:


26

同样的原因,我上班时不开车。我不会使用某些我不会使用的功能。

首先,数组是原始构造,因此数组肯定比List <>更快,更高效,因此您的参数不正确。Array随处可见,并且使用不同的语言和平台的开发人员也知道。

我使用数组而不是List <>的最重要原因是暗示数据是固定长度的。如果我不会从该数据集中添加或删除任何项目,那么我想确保类型能够反映出来。

另一件事是,假设您正在实现一个新的数据结构,并且已经阅读了一些有关它的论文。现在,在实现特定算法时,您不能总是依赖于其他人对通用类型的实现。它从.NET变为Mono,甚至在框架的不同版本之间也有变化。

而且有时移植使用数组而不是依赖于框架的类型的代码更容易。


4
考虑到List<T>使用数组实现的数组比列表“快”得多?如果您事先知道了元素计数(使用数组时必须知道),那么在初始化列表时也可以使用该知识。
Theodoros Chatzigiannakis 2014年

1
@TheodorosChatzigiannakis使用数组时,我们分配内存并简单地进行分配,即简单的malloc(),没有开销。当使用List <>时,将“添加”项目,当添加项目时,List <>进行一些边界检查,并调用surecapacity方法,如果当前容量不足,该方法会将内容复制到另一个新分配的数组中。这意味着,如果您不需要动态分配新内存,则List的性能将不如数组。
Mert Akcakaya 2014年

4
您的意思是正确的,但是它假设要么我们事先不知道元素计数(在这种情况下,数组无论如何都没有用),或者我们确实事先知道了元素计数,但是我们故意不使用就列表而言。如果我们确实使用元素计数来初始化具有所需容量的列表,则只会得到一个数组分配(直到达到该容量),因此我们要付出相同的性能成本。除进一步的添加(所有数组根本没有)之外,所有其他常见操作都是相同的(搜索,按索引检索,按索引分配)。
Theodoros Chatzigiannakis 2014年

3
@TheodorosChatzigiannakis:数组比列表更快。只需运行一个简单的基准进行检查。
Mehrdad 2014年

1
@TheodorosChatzigiannakis,是的,但是仍然存在边界检查。
Mert Akcakaya 2014年

18

当然,您需要数组来管理可变结构的集合,如果没有这些结构,我们将如何处理。

struct EvilMutableStruct { public double X; } // don't do this

EvilMutableStruct[] myArray = new EvilMutableStruct[1];
myArray[0] = new EvilMutableStruct()
myArray[0].X = 1; // works, this modifies the original struct

List<EvilMutableStruct> myList = new List<EvilMutableStruct>();
myList.Add(new EvilMutableStruct());
myList[0].X = 1; // does not work, the List will return a *copy* of the struct

(请注意,在某些情况下,需要一个可变结构的数组,但是通常,数组中可变结构与其他集合的这种不同行为是应避免的错误来源)


更严重的是,如果要通过reference传递元素,则需要一个数组。即

Interlocked.Increment(ref myArray[i]);  // works
Interlocked.Increment(ref myList[i]);   // does not work, you can't pass a property by reference

这对于无锁线程安全代码很有用。


如果您想快速有效地使用默认值初始化固定大小的集合,则需要一个数组。

double[] myArray = new double[1000]; // contains 1000 '0' values
                                     // without further initialisation

List<double> myList = new List<double>(1000) // internally contains 1000 '0' values, 
                                             // since List uses an array as backing storage, 
                                             // but you cannot access those
for (int i =0; i<1000; i++) myList.Add(0);   // slow and inelegant

(请注意,有可能实现List的构造函数执行相同的操作,只是c#不提供此功能)


如果您想有效地复制集合的一部分,则需要一个数组

Array.Copy(array1, index1, array2, index2, length) // can't get any faster than this

double[,] array2d = new double[10,100];
double[] arraySerialized = new double[10*100];
Array.Copy(array2d, 0, arraySerialized, 0, arraySerialized.Length);
// even works for different dimensions

(同样,这也可以为List实现,但是此功能在c#中不存在)


如果假定一种类型表示与管道胶带粘在一起的一组相关但独立的变量(例如,一个点的坐标),则外场结构是最好的表示。如果假定一个变量包含一堆这样的组,则该结构类型的数组是最好的表示。一个人不应该在想要一个对象的地方使用可变结构,但这并不意味着一个人应该在一个想要一堆用胶带粘在一起的变量的地方使用对象或假装为对象的东西。
supercat 2014年

在某些情况下,您可能需要可变的结构,例如,当您需要性能的最后一点并且无法负担堆分配和引用时,在这种情况下,最好使用结构数组。好文章在这里。但是我不同意类不应代表“一堆变量粘在一起”。从概念上讲,结构和类基本相同,差异主要是由于实现细节。
HugoRune 2014年

如果希望使用一组可变对象引用作为一组独立的独立变量集合,则必须建立并保持不变性,即每个元素都标识一个对象,该对象在宇宙中的任何地方都不存在其他非临时引用。当然可以做到这一点(通常是这样做),但是语言或框架中没有任何东西可以帮助程序员坚持不变性。相反,具有四个实例字段的结构类型的length-100数组将自动永久封装400个独立变量。
supercat 2014年

我认为这些评论是进一步讨论此问题的地方,在您添加自己的答案时,似乎没有必要在此处重复此信息。可以说,数组可用于管理可变结构。是否以及何时使用这些结构的实现行为来强制执行某些数据约束确实超出了我的回答范围。
HugoRune 2014年

15

那么,为什么我要使用数组呢?

很少,你将有你在那里的情景知道你需要的元素的固定数量。从设计角度来看,应避免这种情况。如果您需要3件事,那么业务性质意味着您在下一个版本中经常需要4件事。

尽管如此,当这种罕见情况实际发生时,使用数组强制执行固定大小不变式仍然很有用。它向其他程序员发出信号,它的大小是固定的,并有助于防止在有人添加或删除元素的情况下误用-破坏代码其他地方的期望。


23
在编写代码时,数组的大小不是一成不变的。它通常可以是运行时变量。创建数组后,它永远不会改变。

11
处理固定数量的元素并不罕见。如果您正在使用3D点,则将来可能不会再使用5D点。数组的最大问题是它们是易变的,并且没有在其元素上附加方便的标签。
Doval 2014年

3
@JanDvorak列表可以缩小或增长,因此在涉及固定数量元素的任何上下文中都没有意义。
Doval 2014年

9
很少吗 并非所有人都在处理超级复杂的超可配置企业级产品。
whatsisname 2014年

3
固定数量的元素是很常见的。只要确保长度由一个常量定义,那么当下一个发行版将元素的数量从3增加到4时,您只需更改常量,就可以修改所有依赖于此的常量。
汉斯(Hans)

9

您的问题实际上已经得到回答

并且似乎在内存和性能上也与数组一样高效。

不是。从我链接的问题:

List/foreach:  3054ms (589725196)
Array/foreach: 1860ms (589725196)

在某些重要情况下,阵列的速度快两倍。我敢肯定,内存使用情况也很重要。

由于您的问题的主要前提就这样被打败了,我假设这可以回答您的问题。除此之外,有时Win32 API,GPU的着色器或某些其他非DotNet库会强制将数组强加给您。

即使在DotNet中,某些方法也会占用和/或返回数组(例如String.Split)。这意味着要么你现在必须吃调用的成本ToListToArray所有的时间,或者您必须遵守和使用数组,可能是通过传播这对可怜的下游用户持续循环代码。

有关此主题的更多有关堆栈溢出的问题和解答:


有趣的是,该答案指出,如果您使用老式的索引循环(for而不是foreach),则性能差异很小。
user949300 2014年

3

除了其他答案中列出的原因外,数组文字需要更少的字符来声明:

var array = new [] { "A", "B", "C" };
var list = new List<string>() { "A", "B", "C" };

List在以下情况下,使用数组而不是代码会使代码更短一些,并且可读性更高:(1)您需要传递任何IEnumerable<T>文字,或者(2)其他功能List无关紧要,并且您需要使用类似列表的形式文字。

我偶尔在单元测试中这样做。


1
是的,只是要添加。您还需要通过IList <T>
Esben Skov Pedersen

+1,通常我会对不在集合中的两个相同类型的对象进行一些操作。这很容易,简短清晰书写foreach( var x in new []{ a, b, c ) ) DoStuff( x )new []{ a, b, c ).Select( ... )
斯泰恩

3

严格来说,这是从面向对象的角度来看的。

尽管我想不出只传递数组的原因,但我肯定可以看到类内部数组表示可能是最佳选择的情况。

尽管还有其他选项具有类似的特性,但似乎没有一个数组像数组那样直观地处理处理排列,嵌套循环,矩阵表示,位图和数据交织算法。

有很多科学领域广泛依赖矩阵数学。(例如图像处理,数据纠错,数字信号处理,大量应用数学问题)。这些领域中的大多数算法都是根据使用多维数组/矩阵编写的。因此,按照定义的算法来实施算法比让它们变得更“软件”友好而不是以丢失直接与算法所基于的论文的联系为代价的做法更为自然。

就像我说的那样,在这些情况下,您可能可以摆脱使用列表的麻烦,但是在已经很复杂的算法之上又增加了另一层复杂性。


3

实际上,这也适用于具有列表的其他语言(例如Java或Visual Basic)。在某些情况下,您需要使用数组,因为方法会返回数组而不是List。

在实际的程序中,我不认为会经常使用数组,但是有时您会知道数据将是固定大小的,并且您喜欢使用数组所带来的小性能提升。就像进行返回列表的方法或需要多维数据结构一样,微优化将是一个正当的理由。


1
许多语言甚至没有单独的列表和数组集合。另一方面,在C / C ++中使用list<T>where vector<T>将起作用是一个灾难性的坏主意。
操纵机器人

1
“因为方法返回一个数组” –这是Java的一个错误功能,迫使程序员使用“ Arrays.asList(x)”来处理代码。T []应该至少实现Iterable <T>
kevin cline 2014年

4
@StevenBurnap-在C语言中肯定是灾难性的糟糕,因为无法编译该代码。
Pete Becker 2014年

@PeteBecker该表达式vector<T> x 在C中对我来说编译就很好。:-)
svick 2014年

抱歉,我的意思是C手推相当于list<T>。基本上,我已经看到很多性能问题,这些问题是由开发人员在数组是更好的选择时默认情况下仅使用列表而引起的。
2014年

1

好吧,我在自己写的游戏中发现了数组的用途。我用它来创建具有固定数量插槽的库存系统。这有几个好处:

  1. 通过查看并查看哪些插槽为空,我确切地知道了游戏对象有多少个未清库存插槽。
  2. 我确切地知道每个项目所在的索引。
  3. 它仍然是“类型化”数组(Item []库存),因此我可以添加/删除“ Item”类型的对象。

我认为,如果需要“增加”库存的大小,可以通过将旧项目转移到新阵列中来实现,但是由于库存是由屏幕空间固定的,因此无需动态地增加它的大小。 /更小,它很适合我使用的目的。


1

如果要遍历列表的所有元素,则不需要,不需要数组,使用“下一个”或任意“选择而不替换”会很好。

但是,如果您的算法需要随机访问集合中的元素,那么是的,数组是必需的。

这有点类似于“必须去吗?”。用合理的现代语言根本不需要。但是,如果您剥离了某些抽象,那么到那时,您便可以使用所有这些东西,也就是说,实现这些抽象的唯一方法是使用“不必要的”功能。(当然,这种比喻不是完美的,我不认为有人会说数组是不好的编程习惯;它们很容易理解和思考)。


List <T>还提供随机访问。当然,它是通过数组实现的,但是并不需要打扰用户。因此,充其量,您提供了使用数组的单一原因:实现List<T>

@delnan我认为这是关于抽象的观点。有时,最好从数组的角度考虑问题,而不仅仅是出于效率方面的考虑。(当然有时是更好的方式去思考一个链表,和路比这更好的去思考一个列表(没有关于链接或以其他方式它是如何实现的),以及更好的方式只是一个收集爱心。
米奇

0

旧版兼容性。

所有形式的个人经历:

旧版程序员-我的同事在各处使用数组,已经使用了30多年,祝您好运,因为您提出了新想法。

旧版代码-foo(array bar [])确保可以使用列表/向量/集合toarray函数,但如果不使用其中的任何其他功能,则使用数组开始就更容易,而且通常在不进行类型切换的情况下更具可读性。

旧版上司-我的上司在多年前进入管理领域之前是一位优秀的程序员,但她仍然认为自己是最新的,“正在使用数组”可以结束会议,并解释说收集每个人都需要花费午餐。


3
落后曲线20年的同事和一位想知道您使用的数据类型的老板...我喜欢这个网站,以提醒自己我的工作可能会糟得多
JoelFan 2014年

1
大约40%的工作是嵌入式设计,以KB为单位进行讨论,他想告诉我,他还不算过时,他的专业人士笑了
Skeith 2014年

0

1)没有列表的多维版本。如果您的数据具有多个维度,则使用列表的效率将非常低。

2)当您处理大量的小型数据类型(例如,一张地图,其中您所拥有的只是地形类型的一个字节)时,由于缓存的原因,性能可能会存在很大差异。阵列版本每次读取内存会加载几项,列表版本只会加载一项。此外,阵列版本在缓存中保存的单元格是列表版本的几倍。如果重复处理数据,如果阵列版本适合缓存,而列表版本则不能,那么这将有很大的不同。

对于极端情况,请考虑Minecraft。(是的,它不是用C#编写的。有相同的原因。)



0

某个类型T的100个元素的数组封装了类型T的100个独立变量。如果T恰好是具有可变类型Q的公共字段和类型R的值的值类型,则该数组的每个元素将封装独立变量因此,整个数组将封装100个Q型独立变量和100个R型独立变量。这些变量中的任何一个都可以单独访问而不会影响其他变量。 除数组外,其他任何收集类型都不允许将结构的字段用作自变量。

如果T恰好是具有Q和R类型的公共可变字段的类类型,则数组的每个元素在Universe中的任何地方都拥有对的实例的唯一引用T,并且如果该数组的任何元素都不会修改以标识存在外部引用的对象,则该数组将有效封装100个Q类型的独立变量和100个R类型的独立变量。其他集合类型可以模仿此类数组的行为,但前提是唯一的目的数组的封装是封装100个Q类型的变量和100个R类型的变量,将每对变量封装在其自己的类对象中是这样做的昂贵方法。进一步,使用可变类类型的数组或集合可能导致数组元素标识的变量可能不独立

如果一个类型应该表现得像某种对象,那么它应该是一个类类型或一个私有域结构,除了替换之外,它不提供任何变异手段。但是,如果一个类型应该表现得像一堆相关的,但是独立的变量用胶带粘在一起,那么应该使用这样一种类型,一组变量用胶带粘在一起-曝光场结构。这种类型的数组使用起来非常高效,并且具有非常清晰的语义。任何其他类型的使用都将导致语义混乱,性能不佳或两者兼而有之。


0

一个重要的不同是内存分配。例如,遍历一个链表可能会导致大量的高速缓存未命中并降低性能,而数组代表一个连续的内存块,其中包含某些特定数据类型的多个实例,并且遍历它的顺序更可能会击中CPU。缓存。

当然,对象引用数组可能不会从高速缓存命中中获得太多好处,因为取消引用仍然可以将您带到内存中的任何位置。

然后是列表实现,例如ArrayList,它们使用数组实现列表。它们是有用的原语。


2
该问题专门询问有关.Net类型的信息List<T>,该类型不是使用链表实现的,而是使用数组的(基本上与ArrayList<T>Java中的等效)。
svick 2014年

-2

您可以在何时选择Array和何时选择这里使用一些指导List

  • Array从方法返回时使用。
  • 使用List当你正在构建的返回值(在方法内部)变量。然后.ToArray()从方法返回时使用。

通常,Array当您不希望消费者向集合中添加项目时,请使用。使用List时,你想让你的消费者将项目添加到集合中。

Array用于处理“静态”集合,而List用于处理“动态”集合。


2
您建议的规则是任意的,可能会导致执行不必要的复制的代码效率低下。至少您需要某种形式的证明。
Jules

@Jules我发现开发人员的经验比微优化更为重要。我从来没有在这个级别上的性能问题。
DanielLidström2014年

但是,我看不出如何通过此规则来改善开发人员的体验。它要求您猜测代码的客户端将要对返回的值进行哪些操作,并在出错时使代码使用起来不那么方便,但我看不到任何好处。性能可能是次要的问题,但是我不会为了遵循毫无益处的规则而牺牲性能。
Jules 2014年

@Jules我想这取决于您自己的喜好。我更喜欢将返回值视为常量,因此我更喜欢使用Array而不是List。很高兴听到您的想法!
DanielLidström2014年

您是否考虑过使用UmmodifiableLists?
Jules 2014年
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.