最近見つけたコードでArray.Resizeを覚えたので使っていたが、List.Addとの違いや、処理効率がどうなのか気になったので調べた。
先に結論
可変長の配列を扱いたい場合、
Array.Resize
で1要素ずつ増やすよりは、List.Add
とToArray
の組み合わせのほうが効率的。
(ここでは、最終的に余分要素のない配列[]
を作りたい場合を想定している。
AddするたびにToArrayしたい場合はこの結論の限りではない。)
調査環境
Windows10でILSpyでソースを調べて机上で検討した。処理時間の実測はしていない。
ILSpy上の表示は C#7.3 / VS2017.7 となっている。
List<T>
のAdd
とToArray
Listクラス内部コード - Addメソッド
public void Add(T item)
{
if (_size == _items.Length)
{
EnsureCapacity(_size + 1);
}
_items[_size++] = item;
_version++;
}
private void EnsureCapacity(int min)
{
if (_items.Length < min)
{
int num = (_items.Length == 0) ? 4 : (_items.Length * 2);
if ((uint)num > 2146435071u)
{
num = 2146435071;
}
if (num < min)
{
num = min;
}
Capacity = num;
}
}
public int Capacity
{
[__DynamicallyInvokable]
get
{
return _items.Length;
}
[__DynamicallyInvokable]
set
{
if (value < _size)
{
ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.value, ExceptionResource.ArgumentOutOfRange_SmallCapacity);
}
if (value == _items.Length)
{
return;
}
if (value > 0)
{
T[] array = new T[value];
if (_size > 0)
{
Array.Copy(_items, 0, array, 0, _size);
}
_items = array;
}
else
{
_items = _emptyArray;
}
}
}
Add
メソッドが間接的にCapacity
プロパティにsetアクセスして、内部のフィールド_items
(T[]
型)をリサイズ(new T[リサイズ後の要素数]
してArray.Copy
)しています。
Capacity
プロパティのコードをみると、初期値は4で、リサイズするときは2倍ずつ確保しているようです。
Listクラス内部コード - ToArrayメソッド
public T[] ToArray()
{
T[] array = new T[_size];
Array.Copy(_items, 0, array, 0, _size);
return array;
}
Array.Resize
使い方
Array.Resize(ref 配列[], 新たなサイズ);
Microsoft docs
Arrayクラス内部コード
public static void Resize<T>(ref T[] array, int newSize)
{
if (newSize < 0)
{
throw new ArgumentOutOfRangeException("newSize", Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum"));
}
T[] array2 = array;
if (array2 == null)
{
array = new T[newSize];
}
else if (array2.Length != newSize)
{
T[] array3 = new T[newSize];
Copy(array2, 0, array3, 0, (array2.Length > newSize) ? newSize : array2.Length);
array = array3;
}
}
サイズが変わると、増減によらずnew T[]
してArray.Copy
を呼んでいます。
単純にArray.Copyの計算量が要素数nに比例する(仮にnとおく)とすると、
1要素ずつResizeメソッドで要素を1からnまで増やすと、計算量は1+2+3+...+n = n*(n+1)/2となるので計算量はn^2に比例します。
List.Addの場合のArray.Copyの発生コストを単純計算で考えると、1,2,4,8,16,32,...,n/4,n/2,nのときにArray.Sizeが呼ばれるので1、その和は2n-1となり、計算量はnに比例するはず(細かいことは考えていないのでちょっと怪しいが)。
結論 (計算量の観点)
1要素ずつ要素数をnまで増やした場合について、単純計算で考えると
- Array.Resizeでは、要素数nの2乗に比例する計算量となる。
- List.Add(と1回のToArray)では、要素数nに比例する計算量となる。
そのため、Listを使うほうが計算量的には優位と判断できる。
■計算量のオーダーついての注意:
実用上のnの範囲では、単純にオーダーだけでは議論できない場合がある。2