164

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

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

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

但是,由于我喜欢与之合作,因此List<>我决定放入...

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

那么,在决定进行这样的转换时是否应该考虑某种性能影响 - 或者只在处理大量文件时考虑?这是一个微不足道的转换吗?

4

8 回答 8

199

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>如果您有兴趣,这里有一些关于如何工作的更多细节:)

AList<T>还使用一种称为动态数组的结构,该结构需要按需调整大小,此调整大小事件将旧数组的内容复制到新数组。所以它从小开始,如果需要,它的大小会增加

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

于 2013-03-20T07:20:00.377 回答
42

调用 toList() 时是否会影响性能?

是的当然。从理论上讲,它甚至i++会对性能产生影响,它会使程序减慢几个滴答声。

做什么.ToList

当您调用.ToList时,代码会调用Enumerable.ToList()一个扩展方法,即return new List<TSource>(source). 在对应的构造函数中,最坏的情况下,它会遍历item container,将它们一个一个添加到一个新的容器中。所以它的行为对性能影响很小。成为应用程序的性能瓶颈是不可能的。

问题中的代码有什么问题

Directory.GetFiles遍历文件夹并将所有文件的名称立即返回到内存中,字符串 [] 可能会占用大量内存,从而减慢一切速度。

那应该怎么办

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

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

一旦找到名称包含“myfile”的文件,它将停止搜索路径。这显然是有更好的表现呢.GetFiles

于 2013-03-20T06:43:06.960 回答
22

调用 toList() 时是否会影响性能?

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

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

List<T>使用数组 ( T[]) 来存储列表的元素。数组一旦分配就无法扩展,因此List<T>将使用超大数组来存储列表的元素。当List<T>增长超过底层数组的大小时,必须分配一个新数组,并且必须将旧数组的内容复制到新的更大的数组中,然后列表才能增长。

List<T>当从 an 构造一个 new 时,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>对象中。即使是第二种情况的性能,对于小型集合来说也不必担心。

于 2013-03-20T07:32:03.807 回答
5

它将与执行以下操作一样(无效):

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

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

  • Call collection.Count,所以如果collection是 an IEnumerable<T>,它将强制执行。如果collection是数组、列表等,它应该是O(1).

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

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

所以,是的,它会消耗更多的内存,因为它必须创建一个新列表,在最坏的情况下,它会是O(n),因为它会遍历collection以复制每个元素。

于 2013-03-20T06:35:48.247 回答
5

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

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

从这个角度来看,影响肯定可以忽略不计,以至于不需要考虑。

但只有当你真的需要List<>结构的特性来提高你的生产力,或者你的算法更友好,或者其他一些优势时。否则,你只是故意添加一个微不足道的性能影响,完全没有理由。在这种情况下,您自然不应该这样做!:)

于 2013-03-20T07:12:29.003 回答
4

ToList()创建一个新的 List 并将元素放入其中,这意味着 do 有相关的成本ToList()。在小集合的情况下,成本不会很明显,但是在使用 ToList 的情况下,拥有大量集合可能会导致性能下降。

通常你不应该使用 ToList() 除非你正在做的工作不能在不将集合转换为列表的情况下完成。例如,如果您只想遍历集合,则不需要执行 ToList

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

于 2013-03-20T06:22:55.413 回答
3

考虑到检索文件列表的性能,ToList()可以忽略不计。但并非真的适用于其他场景。这真的取决于你在哪里使用它。

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

    在您的示例中,您在数组上调用它。它遍历数组并将项目一项一项添加到新创建的列表中。所以性能影响取决于文件的数量。

  • 当调用 an IEnumerable<T>时,您会具体IEnumerable<T>(通常是查询)。

于 2013-03-20T06:23:42.417 回答
2

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

于 2013-03-20T06:25:13.877 回答