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();
当我从stopwach1
和stopwach2
获取 4 结果时,stopwatch1
的值始终低于stopwatch2
。这意味着addrange
总是比foreach
更快。
有谁知道为什么?
答案 0 :(得分:83)
可能AddRange
可以检查传递给它的值在哪里实现IList
或IList<T>
。如果是,它可以找出该范围内有多少个值,因此需要分配多少空间......而foreach
循环可能需要重新分配几次。
此外,即使在分配后,List<T>
也可以使用IList<T>.CopyTo
执行批量复制到基础数组中(当然,对于实现IList<T>
的范围。)
我怀疑你会发现,如果你再次尝试测试,但使用Enumerable.Range(0, 100000)
代替fillData
代替List<T>
,那么两者大约需要同一时间。
答案 1 :(得分:55)
如果您正在使用Add
,则会根据需要(加倍)逐步调整内部数组的大小,从默认的起始大小10(IIRC)开始。如果您使用:
var manualFill = new List<int>(fillData.Count);
我希望它会彻底改变(不再调整大小/数据副本)。
从反射器,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 ^^^
答案 2 :(得分:17)
因为AddRange
检查添加项的大小并且仅增加内部数组的大小一次。
答案 3 :(得分:6)
List AddRange方法的反射器反汇编具有以下代码
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()。
答案 4 :(得分:5)
使用AddRange
时,Collection可以增加一次数组的大小,然后将值复制到其中。
使用foreach
语句,集合需要多次增加集合的大小。
增加thr大小意味着复制需要时间的完整数组。
答案 5 :(得分:4)
这就像要求服务员给你一杯啤酒十次并要求他一次给你带来10瓶啤酒。
您认为什么更快:)
答案 6 :(得分:2)
我想这是优化内存分配的结果。 对于AddRange,内存只分配一次,并且每次迭代时都会重新分配。
也可能在AddRange实现中有一些优化(例如memcpy)
答案 7 :(得分:1)
在手动添加项目之前尝试初始化初始列表容量:
var manualFill = new List<int>(fillData.Count);
答案 8 :(得分:1)
这是因为Foreach循环将添加循环每次获取的所有值和
AddRange()方法将收集它所获得的所有值作为&#34; chunk&#34;并将该块一次添加到指定位置。
简单地理解,就像你有一个从市场上带来的10个项目的清单,这将更快地将所有这些项目一次性或全部一次性地带来。
答案 9 :(得分:0)
AddRange扩展不会遍历每个项目,而是将每个项目作为一个整体应用。 foreach和.AddRange都有一个目的。 Addrange将赢得当前形势的速度竞赛。
更多相关信息: