调用ToList()会对性能产生影响吗?


139

使用时ToList(),是否需要考虑对性能的影响?

我正在编写一个查询以从目录中检索文件,该查询是:

string[] imageArray = Directory.GetFiles(directory);

但是,由于我想与之合作List<>,所以我决定投入...

List<string> imageList = Directory.GetFiles(directory).ToList();

因此,在决定进行这种转换时是否应该考虑某种性能影响?或者仅在处理大量文件时才考虑?这是微不足道的转换吗?


+1也想知道这里的答案。恕我直言,除非该应用程序对性能至关重要,否则我认为如果代码使代码更具有逻辑性/可读性/可维护性,我总是会使用a List<T>代替T[](除非转换当然引起明显的性能问题,在这种情况下,我会重新访问它,我猜)。
2013年

从数组创建列表应该非常便宜。
leppie

2
@Sepster我仅根据需要完成的工作专门指定数据类型。如果我不必致电AddRemove,我将其保留为IEnumerable<T>(甚至更好var
pswg

4
我认为,在这种情况下最好调用EnumerateFiles而不是GetFiles,因此将只创建一个数组。
tukaef

3
GetFiles(directory),因为它目前在.NET中实现,所以几乎可以做到new List<string>(EnumerateFiles(directory)).ToArray()。因此GetFiles(directory).ToList()创建一个列表,从中创建一个数组,然后再次创建一个列表。就像2kay所说的那样,您应该更喜欢在EnumerateFiles(directory).ToList()这里做。
Joren 2013年

Answers:


178

IEnumerable.ToList()

是的,IEnumerable<T>.ToList()确实对性能有影响,尽管可能只需要关注性能关键的操作,但它是O(n)操作。

ToList()操作将使用List(IEnumerable<T> collection)构造函数。此构造函数必须复制数组(通常是IEnumerable<T>),否则将来对原始数组的修改也会在源上更改,T[]这通常是不希望的。

我要重申的是,这只会对庞大的列表有所作为,复制内存块是一项非常快速的操作。

方便的提示,AsvsTo

您会在LINQ中注意到有几种以As(如AsEnumerable())和To(如ToList())开头的方法。开头的方法To需要进行上述转换(即可能会影响性能),而开头的方法As则不需要,只需要进行一些强制转换或简单操作即可。

有关的其他详细信息 List<T>

List<T>如果您有兴趣,这里有一些工作原理的详细信息:)

A List<T>还使用称为动态数组的结构,该结构需要按需调整大小,此调整大小事件将旧数组的内容复制到新数组。因此,它从很小的地方开始,并在需要时增加尺寸

这是的CapacityCount属性之间的区别List<T>Capacity指的是幕后数组的大小,Count是其中的项目数List<T>始终为<= Capacity。因此,当将一项添加到列表中时,将其增加到之后Capacity,的大小将List<T>增加一倍,并复制该数组。


2
我只是想强调一下,List(IEnumerable<T> collection)构造函数将检查collection参数是否为真ICollection<T>,然后立即创建一个具有所需大小的新内部数组。如果参数collection不是ICollection<T>,则构造函数将对其进行遍历并调用Add每个元素。
Justinas Simanavicius

重要的是要注意,您可能经常将ToList()看作是一个误导性的操作。通过LINQ查询创建IEnumerable <>时会发生这种情况。linq查询已构建但未执行。调用ToList()将运行查询,因此似乎
占用

36

调用toList()会对性能产生影响吗?

当然是。从理论上讲甚至i++会对性能产生影响,可能会使程序变慢几格。

怎么.ToList办?

调用时.ToList,代码将调用Enumerable.ToList()作为扩展方法的return new List<TSource>(source)。在相应的构造函数中,在最坏的情况下,它将遍历item容器并将它们一个接一个地添加到新容器中。因此,它的行为对性能几乎没有影响。成为应用程序的性能瓶颈是不可能的。

问题中的代码有什么问题

Directory.GetFiles遍历该文件夹并将所有文件的名称立即返回到内存中,这有潜在的风险,即string []占用大量内存,从而减慢了所有工作。

那应该怎么办

这取决于。如果您(以及您的业务逻辑)保证该文件夹中的文件量始终很小,则该代码是可以接受的。但是仍然建议使用懒惰版本:Directory.EnumerateFiles在C#4中。这更像是查询,不会立即执行,您可以在其上添加更多查询,例如:

Directory.EnumerateFiles(myPath).Any(s => s.Contains("myfile"))

一旦找到名称包含“ myfile”的文件,它将立即停止搜索路径。显然这具有更好的性能.GetFiles


19

调用toList()会对性能产生影响吗?

就在这里。使用扩展方法Enumerable.ToList()将从源集合构造一个新List<T>对象,IEnumerable<T>这当然会对性能产生影响。

但是,了解List<T>可能会帮助您确定性能影响是否重大。

List<T>使用数组(T[])存储列表的元素。数组一旦分配就无法扩展,因此List<T>将使用超大数组来存储列表中的元素。当List<T>增长超过基础数组的大小时,必须分配新数组,并且必须在列表可以增长之前将旧数组的内容复制到新的较大数组。

List<T>从构造新对象时,IEnumerable<T>有两种情况:

  1. 源集合的实现ICollection<T>:然后ICollection<T>.Count用于获取源集合的确切大小,并在将源集合的所有元素复制到支持数组之前,分配一个匹配的支持数组ICollection<T>.CopyTo()。此操作非常有效,可能会映射到某些CPU指令以复制内存块。但是,就性能而言,新阵列需要内存,复制所有元素需要CPU周期。

  2. 否则源集合的大小是未知的,并且使用的枚举器IEnumerable<T>将每个源元素一次添加到新元素中List<T>。最初,支持数组为空,并且创建了大小为4的数组。然后,当此数组太小时,其大小将增加一倍,因此后备数组将像4、8、16、32等那样增长。每当后备数组增长时,都必须重新分配它,并且必须复制到目前为止存储的所有元素。与可以立即创建正确大小的数组的第一种情况相比,此操作的成本要高得多。

    同样,如果您的源集合包含33个元素,则该列表最终将使用64个元素的数组浪费一些内存。

在您的情况下,源集合是一个实现的数组,ICollection<T>因此,除非源数组很大,否则您不必担心性能影响。调用ToList()将只复制源数组并将其包装在一个List<T>对象中。即使是第二种情况的性能,也不必为小收藏而担心。


5

“是否需要考虑性能影响?”

精确方案的问题是,首先,您对性能的真正关注将来自硬盘的速度和驱动器缓存的效率。

从这个角度来看,影响是可以忽略不计肯定该点NO就不必考虑。

但是仅当您确实需要List<>结构的功能以使您更高效,算法更友好或具有其他优势时,才可以。否则,您只是故意添加了微不足道的性能影响,根本没有任何原因。在这种情况下,自然不应该这样做!:)


4

ToList()创建一个新的List并将元素放入其中,这意味着与之相关的成本ToList()。如果收集的数量很少,则花费不会很明显,但是如果使用ToList,则收集的数量过多会导致性能下降。

通常,除非您不将collection转换为List便无法完成工作,否则不应该使用ToList()。例如,如果您只想遍历集合,则无需执行ToList

如果您要对数据源(例如使用LINQ to SQL的数据库)执行查询,那么执行ToList的成本会更高,因为当您将LINQ to SQL与ToList一起使用时,而不是进行延迟执行,即在需要时加载项目(这可能是有益的)在许多情况下)它将立即将项目从数据库加载到内存中


哈里斯:我不确定原始来源是什么,在调用ToList()之后,原始来源会发生什么
TalentTuner 2013年

@Saurabh GC将清理它
pswg

@Saurabh对原始来源不会有任何反应。新创建的列表将引用原始资源的元素
Haris Hasan

“如果只想遍历集合,则不需要执行ToList”-那么应该如何迭代呢?
SharpC

4

它的效率与以下方法一样:

var list = new List<T>(items);

如果您反汇编采用的构造函数的源代码IEnumerable<T>,则会看到它会做一些事情:

  • 调用collection.Count,所以如果collectionIEnumerable<T>,它将强制执行。如果collection是数组,列表等,则应为O(1)

  • 如果是collectionImplements ICollection<T>,它将使用ICollection<T>.CopyTo方法将项目保存在内部数组中。它应该O(n),是n集合的长度。

  • 如果collection未实现ICollection<T>,它将遍历集合的各项,并将它们添加到内部列表中。

因此,是的,它将消耗更多的内存,因为它必须创建一个新列表,而在最坏的情况下,它将是O(n),因为它将循环访问collection以制作每个元素的副本。


3
近,0(n)其中,n是的原始集合中的字符串的字节的总和占据,而不是单元的计数(以及更精确地说N =字节/字大小)
user1416420

@ user1416420我可能错了,但是为什么呢?如果是一些什么其他类型(例如,一个集合boolint等等)?您实际上不必为集合中的每个字符串制作一个副本。您只需将它们添加到新列表中。
奥斯卡·梅德罗斯

仍然没关系,新的内存分配和字节复制是杀死此方法的原因。在.NET中,布尔也将占用4个字节。实际上,.NET中对象的每个引用至少有8个字节长,因此它非常慢。前4个字节指向类型表,后4个字节指向值或在其中查找值的存储位置
user1416420 2013年

3

考虑到检索文件列表的性能,ToList()可以忽略不计。但对于其他情况并非如此。这实际上取决于您在哪里使用它。

  • 调用数组,列表或其他集合时,可以将集合的副本创建为List<T>。这里的性能取决于列表的大小。您应该在真正必要时这样做。

    在您的示例中,您在数组上调用它。它遍历数组,并将这些项一个接一个地添加到新创建的列表中。因此,性能影响取决于文件数。

  • 当一个呼叫 IEnumerable<T>,你兑现IEnumerable<T>(通常是一个查询)。


2

ToList将创建一个新列表,并将元素从原始源复制到新创建的列表中,因此唯一的事情是从原始源复制元素,具体取决于源大小

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.