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

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

我正在写一个查询来从目录中检索文件,这是查询:

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

但是,因为我喜欢与List<>工作,所以我决定放入…

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

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

IEnumerable.ToList()

是的, IEnumerable<T>.ToList()确实会对性能产生影响,这是一个O(n)操作,尽pipe在性能关键操作中可能只需要注意。

ToList()操作将使用List(IEnumerable<T> collection)构造函数。 这个构造函数必须复制一个数组(更一般的IEnumerable<T> ),否则对原始数组的未来修改也会在源T[]上发生变化,这通常是不可取的。

我想重申一下,这只会与一个巨大的列表有所不同,复制大块内存是相当快的一个操作。

方便的提示, As vs

你会注意到在LINQ中有几个以As开头的方法(如AsEnumerable() )和To (如ToList() )。 以To开始的方法需要像上面那样的转换(即可能会影响性能),以As开头的方法不需要,只需要一些转换或简单的操作。

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

这里有一些关于List<T>如何工作的细节,以防你感兴趣:)

一个List<T>也使用一个叫做dynamic数组的构造,它需要根据需要resize,这个resize事件将旧数组的内容复制到新数组中。 所以如果需要的话,它会从小尺寸开始增加尺寸 。

这是List<T>上的CapacityCount属性之间的区别。 Capacity是指幕后数组的大小, CountList<T>中总是<= Capacity 。 所以当一个项目被添加到列表中,增加它超过CapacityList<T>的大小加倍,数组被复制。

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

当然是。 理论上,即使i++对性能有影响,它也会使程序的运行速度减慢几个小时。

.ToList做什么的?

当您调用.ToList ,代码会调用Enumerable.ToList() ,这是一个return new List<TSource>(source)的扩展方法。 在相应的构造函数中, 在最坏的情况下,它通过物品容器并将它们逐个添加到新的容器中。 所以它的行为对性能影响不大。 要成为应用程序的性能瓶颈是不可能的。

问题中的代码有什么问题

Directory.GetFiles遍历该文件夹,并立即将所有文件的名称返回到内存中,这有潜在的风险,即string[]耗费大量内存,使所有内容变慢。

那么应该做些什么呢?

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

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

一旦find名为“myfile”的文件,将停止searchpath。 这显然有更好的performance.GetFiles

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

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

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

List<T>使用数组( T[] )来存储列表的元素。 数组一旦被分配就不能被扩展,所以List<T>将使用一个超大的数组来存储列表的元素。 当List<T>超出大小的基础数组时,必须分配一个新数组,并且必须将旧数组的内容复制到新的较大数组中,然后列表才能增长。

当从IEnumerable<T>构造一个新的List<T> IEnumerable<T> ,有两种情况:

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

  2. 否则,源集合的大小是未知的,并且IEnumerable<T>的枚举IEnumerable<T>用于将每个源元素一次添加到新的List<T> 。 最初支持数组是空的,并创build一个大小为4的数组。 那么当这个数组太小时,size就会增加一倍,所以backing数组就像这个4,8,16,32等一样增长。每当backing数组增长的时候,它都必须被重新分配,并且所有存储的元素都必须被复制。 与第一种情况相比,这种操作成本更高,可以立即创build正确大小的数组。

    此外,如果你的源代码集合包含33个元素,那么这个列表将最终使用一个64个元素的数组,浪费一些内存。

在你的情况下,源集合是一个实现了ICollection<T>的数组,所以性能影响不是你应该关心的,除非你的源数组非常大。 调用ToList()将简单地复制源数组并将其包装在List<T>对象中。 即使是第二种情况的performance也不足为小的collections担心。

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

精确场景的问题在于,首先,您对性能的真正关注来自硬盘caching的硬盘驱动器速度和效率。

从这个angular度来看,这个影响肯定是微不足道的,它不需要考虑。

但是只有当你确实需要List<>结构的特性,才有可能让你更有效率,或者你的algorithm更友好,或者其他一些优势。 否则,你只是故意添加一个微不足道的performance,毫无理由。 在这种情况下,你自然不应该这样做! 🙂

ToList将创build一个新的列表,并将元素从原始来源复制到新创build的列表中,所以唯一的办法是从原始来源复制元素,并依赖于来源大小

ToList()创build一个新的List,并将其中的元素,这意味着有做ToList()相关的成本。 在收集小的情况下,它不会是非常明显的成本,但有一个巨大的收集可以导致使用ToList的情况下的性能打击。

一般来说,你不应该使用ToList(),除非你所做的工作不能在没有将集合转换为List的情况下完成。 例如,如果您只想遍历集合,则不需要执行ToList

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

它将像在做一样有效率:

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

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

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

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

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

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

考虑到检索文件列表的性能, ToList()是微不足道的。 但其他情况并不是真的。 这真的取决于你在哪里使用它。

  • 在调用数组,列表或其他集合时,将以List<T>创build集合的副本。 这里的performance取决于列表的大小。 你真的有必要的时候应该这样做。

    在你的例子中,你可以在数组上调用它。 它遍历数组并逐个将项添加到新创build的列表中。 所以性能影响取决于文件的数量。

  • 当调用一个IEnumerable<T> ,你实现 IEnumerable<T> (通常是一个查询)。