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

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

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

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

然而,因为我喜欢与 List<>工作,而不是,我决定放入..。

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

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

72220 次浏览

ToList()创建一个新的 List 并将元素放入其中,这意味着执行 ToList()会产生相关的成本。对于较小的集合,成本不是很明显,但是如果使用 ToList,拥有大量的集合可能会导致性能下降。

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

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

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

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

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

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

ToList 将创建一个新列表,并将元素从原始源复制到新创建的列表中,因此只需要从原始源复制元素并取决于源的大小

它将与实际操作一样有效:

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

如果您反汇编接受 IEnumerable<T>的构造函数的源代码,您将看到它将执行以下操作:

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

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

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

因此,是的,它将消耗更多的内存,因为它必须创建一个新的列表,还有 在最坏的情况下,它将是 O(n),因为它将遍历 collection以创建每个元素的副本。

调用 toList ()对性能有影响吗?

是的,当然。理论上,即使是 i++也会对性能产生影响,它可能会使程序运行速度减慢几秒钟。

.ToList是做什么的?

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

问题中的代码出了什么问题

Directory.GetFiles遍历该文件夹并将所有文件的名称 马上返回到内存中,它有一个潜在的风险,即字符串[]占用大量内存,使一切变慢。

那该怎么办

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

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

这将停止 寻找的路径,只要一个文件的名称包含“ myfile”被发现。这显然比 .GetFiles有更好的性能。

“是否需要考虑绩效影响?”

您的精确场景的问题在于,首先,也是最重要的,您对性能的真正关注将来自硬盘驱动器的速度和驱动器缓存的效率。

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

但只有当你真正需要的特点,List<>结构,可能使您更有效率,或您的算法更友好,或其他一些优势。否则,您就是在无缘无故地故意添加一个无关紧要的性能损失。这种情况下,你当然不应该这么做!:)

IEnumerable<T>.ToList()

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

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

我想重申的是,对于一个庞大的列表来说,这只会产生不同的结果,复制内存块是一个相当快速的操作。

小贴士,AsTo

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

有关 List<T>的其他详情

如果您感兴趣的话,这里有一些关于 List<T>如何工作的更多细节:)

List<T>还使用一个称为动态数组的结构,该结构需要根据需要调整大小,此调整大小事件将旧数组的内容复制到新数组。因此,它开始小和 如有需要,增加规模

这是 Count1上 CapacityCount0属性之间的区别。Capacity指的是幕后数组的大小,CountList<T>中的项目数,总是 <= Capacity。因此,当一个项目被添加到列表中,并将其增加到超过 Capacity时,List<T>的大小将增加一倍,数组将被复制。

调用 toList ()对性能有影响吗?

有的。使用扩展方法 Enumerable.ToList()将从 IEnumerable<T>源集合构造一个新的 List<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>对象中。对于小型集合,甚至第二种情况的性能也不需要担心。

让我们看另一个例子;

如果您在使用数据库时运行 ToList()方法并检查 SQLProfiler 中的此代码;

var IsExist = (from inc in entities.be_Settings
where inc.SettingName == "Number"
select inc).ToList().Count > 0;

自动创建的查询如下:

SELECT [Extent1].[SettingName] AS [SettingName], [Extent1].[SettingValue] AS [SettingValue] FROM [dbo].[be_Settings] AS [Extent1] WHERE N'Number' = [Extent1].[SettingName]

使用 ToList 方法运行 select 查询,查询结果存储在内存中,并通过查看 List 的元素数来检查是否有记录。例如,如果表中有1000条记录符合相关标准,这1000条记录首先从数据库中提取出来并转换为对象,然后将它们抛入 List 中,您只需检查该 List 中的元素数。所以这是一种非常低效的选择方式。

它不完全是关于列表性能,但如果你有高维数组 可以使用 HashSet 而不是 List。