我正在使用.Net 3.5(C#),我听说C#List<T>.ToArray
的性能“糟糕”,因为它为所有元素的内存副本形成一个新数组。这是真的吗?
答案 0 :(得分:49)
不,那不是真的。性能很好,因为它所做的只是内存复制所有元素(*)以形成一个新数组。
当然,这取决于你所定义的“好”或“坏”表现。
(*)引用类型的引用,值类型的值。
修改强>
在回复您的评论时,使用Reflector是检查实施的好方法(见下文)。或者只考虑几分钟如何实现它,并相信微软的工程师不会提出更糟糕的解决方案。
public T[] ToArray()
{
T[] destinationArray = new T[this._size];
Array.Copy(this._items, 0, destinationArray, 0, this._size);
return destinationArray;
}
当然,“好”或“坏”表现只相对于某些选择具有意义。如果在您的具体情况下,有一种替代技术可以更快地实现您的目标,那么您可以将性能视为“糟糕”。如果没有这样的选择,那么表现就是“好”(或“足够好”)。
编辑2
回应评论:“没有重建物体?” :
无需重建参考类型。对于值类型,将复制值,这可能会被松散地描述为重建。
答案 1 :(得分:19)
调用ToArray()
的原因不要调用ToArray()
的原因取自here
答案 2 :(得分:4)
是的,它确实是所有元素的内存副本。这是性能问题吗?这取决于您的性能要求。
List
内部包含一个数组,用于存放所有元素。如果容量不再足够列表,则阵列会增长。只要发生这种情况,列表就会将所有元素复制到一个新数组中。这种情况一直都在发生,对大多数人来说都不是性能问题。
E.g。具有默认构造函数的列表从容量16开始,当您.Add()
第17个元素时,它创建一个大小为32的新数组,复制16个旧值并添加第17个。
大小差异也是ToArray()
返回新数组实例而不是传递私有引用的原因。
答案 3 :(得分:1)
它在数组中创建了新的引用,但这只是该方法可以而且应该做的唯一事情......
答案 4 :(得分:1)
必须以相对的方式理解表现。将数组转换为List涉及复制数组,其成本取决于数组的大小。但是你必须将这个成本与你的程序正在做的其他事情进行比较。你是如何获得首先放入阵列的信息的?如果是通过从磁盘,网络连接或数据库读取,则内存中的数组副本不太可能对所花费的时间产生可检测的差异。
答案 5 :(得分:1)
这就是微软official documentation关于List.ToArray时间复杂度的说法
使用Array.Copy复制元素,这是一个O(n)操作,其中n是Count。
然后looking at Array.Copy,我们看到它通常不是克隆数据,而是使用引用:
如果sourceArray和destinationArray都是引用类型的数组,或者都是Object类型的数组,则执行浅表复制。数组的浅表副本是包含与原始数组相同元素的引用的新数组。不会复制元素本身或元素引用的任何内容。相反,数组的深层副本将复制元素以及元素直接或间接引用的所有内容。
因此,总而言之,这是从列表中获取数组的一种非常有效的方法。
答案 6 :(得分:0)
对于知道长度的任何类型的List / ICollection,它可以从一开始就分配一个大小合适的数组。
T[] destinationArray = new T[this._size];
Array.Copy(this._items, 0, destinationArray, 0, this._size);
return destinationArray;
如果你的源类型是IEnumerable(不是List / Collection),那么源是:
items = new TElement[4];
..
if (no more space) {
TElement[] newItems = new TElement[checked(count * 2)];
Array.Copy(items, 0, newItems, 0, count);
items = newItems;
它从4号开始并以指数方式增长,每次空间耗尽时加倍。每次加倍时,都必须重新分配内存并复制数据。
如果我们知道源数据大小,我们可以避免这种轻微的开销。然而,在大多数情况下,例如阵列大小<= 1024,它将执行得如此之快,以至于我们甚至不需要考虑这个实现细节。
引用:Enumerable.cs,List.cs(F12ing into the),Joe的回答