为什么AddRange比使用foreach循环更快?

var fillData = new List<int>(); for (var i = 0; i < 100000; i++) { fillData.Add(i); } var stopwatch1 = new Stopwatch(); stopwatch1.Start(); var autoFill = new List<int>(); autoFill.AddRange(fillData); stopwatch1.Stop(); var stopwatch2 = new Stopwatch(); stopwatch2.Start(); var manualFill = new List<int>(); foreach (var i in fillData) { manualFill.Add(i); } stopwatch2.Stop(); 

当我从stopwach1stopwach2 4个结果时, stopwatch1值始终低于stopwatch2 。 这意味着addrange总是比foreach快。 有谁知道为什么?

AddRange可能会检查传递给它的值实现IListIList<T> 。 如果是这样,它可以找出有多less值在该范围内,因此需要分配多less空间…而foreach循环可能需要重新分配多次。

另外,即使在分配之后, List<T>也可以使用IList<T>.CopyTo来执行批量复制到底层数组(对于实现IList<T>的范围当然是)。

我怀疑你会发现,如果你再次尝试你的testing,但使用Enumerable.Range(0, 100000) fillData Enumerable.Range(0, 100000) fillData而不是一个List<T> ,两者将花费大致相同的时间。

如果使用Add ,它会根据需要逐渐调整内部数组的大小(加倍),默认大小为10(IIRC)。 如果你使用:

 var manualFill = new List<int>(fillData.Count); 

我预计它会发生根本性的变化(不再resize/数据复制)。

从reflection器, AddRange内部,而不是增加倍增:

 ICollection<T> is2 = collection as ICollection<T>; if (is2 != null) { int count = is2.Count; if (count > 0) { this.EnsureCapacity(this._size + count); // ^^^ this the key bit, and prevents slow growth when possible ^^^ 

因为AddRange检查添加项目的大小,并增加一次内部数组的大小。

List AddRange方法的reflection器的反汇编具有以下代码

 ICollection<T> is2 = collection as ICollection<T>; if (is2 != null) { int count = is2.Count; if (count > 0) { this.EnsureCapacity(this._size + count); if (index < this._size) { Array.Copy(this._items, index, this._items, index + count, this._size - index); } if (this == is2) { Array.Copy(this._items, 0, this._items, index, index); Array.Copy(this._items, (int) (index + count), this._items, (int) (index * 2), (int) (this._size - index)); } else { T[] array = new T[count]; is2.CopyTo(array, 0); array.CopyTo(this._items, index); } this._size += count; } } 

正如你所看到的,有一些优化如EnsureCapacity()调用和使用Array.Copy()。

使用AddRange ,Collection可以增加一次数组的大小,然后将值复制到其中。

使用foreach语句集合需要不止一次地增加集合的大小。

增加thr大小意味着复制需要时间的完整数组。

这就像是要求服务员给你带来一瓶啤酒十次,并要求他立即带上10杯啤酒。

你认为什么是更快:)

我想这是内存分配优化的结果。 AddRange内存只分配一次,而foreach每次迭代重新分配完成。

也可能在AddRange实现中有一些优化(例如memcpy)

这是因为Foreach循环会添加循环所获得的所有值
AddRange()方法将收集所有作为“块”获取的值,并将该块立即添加到指定的位置。

简单地理解,就像你有一个从市场上拿来的10件物品的清单,这将更快地把所有这一切,一个接一个或所有在一次。

AddRange扩展不会遍历每个项目,而是应用每个项目作为一个整体。 foreach和.AddRange都有一个目的。 Addrange将为您目前的情况赢得速度的比赛。

更多关于这里:

Addrange与Foreach

在手动添加项目之前尝试初始化初始列表容量:

 var manualFill = new List<int>(fillData.Count);