最近見つけたコードで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メソッド
publicvoidAdd(Titem){if(_size==_items.Length){EnsureCapacity(_size+1);}_items[_size++]=item;_version++;}
privatevoidEnsureCapacity(intmin){if(_items.Length<min){intnum=(_items.Length==0)?4:(_items.Length*2);if((uint)num>2146435071u){num=2146435071;}if(num<min){num=min;}Capacity=num;}}
publicintCapacity{[__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=newT[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メソッド
publicT[]ToArray(){T[]array=newT[_size];Array.Copy(_items,0,array,0,_size);returnarray;}
Array.Resize
使い方
Array.Resize(ref 配列[], 新たなサイズ);
Microsoft docs
Arrayクラス内部コード
publicstaticvoidResize<T>(refT[]array,intnewSize){if(newSize<0){thrownewArgumentOutOfRangeException("newSize",Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum"));}T[]array2=array;if(array2==null){array=newT[newSize];}elseif(array2.Length!=newSize){T[]array3=newT[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