|
|
|
|
|
前面介紹過(guò)官方建議使用List而不是ArryList,雖然理論上那樣說(shuō),但沒(méi)有經(jīng)過(guò)實(shí)驗(yàn)測(cè)試比較,不太能令人信服。出于好奇心,便測(cè)試了一下C# ArryList
與List
的性能差異,結(jié)果有點(diǎn)意外。
前幾天,一位朋友問(wèn)了我一個(gè)簡(jiǎn)單的問(wèn)題,我看了看他的代碼,他沒(méi)有使用 List<T>
,而是使用了類型“ArrayList
”。老實(shí)說(shuō),我甚至不記得上次使用 ArrayList
是什么時(shí)候了。我認(rèn)為當(dāng)我開(kāi)始在 .NET 2 中編程時(shí),我可能無(wú)法足夠快地理解泛型,而 ArrayList
似乎是一個(gè)替代品。
有什么不同?
兩者之間的主要區(qū)別在于 ArrayList
僅包含“對(duì)象”類型,這意味著理論上它是一盒你想要的任何東西,例如這段代碼編譯得很好:
ArrayList arrayList = new ArrayList();
arrayList.Add(123);
arrayList.Add("abc");
arrayList.Add(new object());
然后在代碼上從數(shù)組列表中抓取內(nèi)容以“檢查”它的類型是否正確。在實(shí)踐中,你不會(huì)隨意地將各種類型放入數(shù)組列表中,因此實(shí)際上它更像是編譯時(shí)的“松散”。如果我們將它與 List
進(jìn)行比較:
List<int> list = new List<int>();
list.Add(123);
list.Add("abc"); //編譯時(shí)錯(cuò)誤
它知道我們只想存儲(chǔ)整數(shù)并且試圖將任何其他東西塞進(jìn)去是行不通的。
但是這個(gè)呢?
List<object> list = new List<object>();
list.Add(123);
list.Add("abc");
這行得通嗎?對(duì)象列表幾乎與 ArrayList
相同。
如果我們查看 ArrayList
實(shí)現(xiàn)的接口:
public class ArrayList : ICollection, IEnumerable, IList, ICloneable
List
基本上與一些通用接口相同,但是當(dāng)你檢查這些時(shí),除了使用類型的“Add
”等通用方法之外,它們不會(huì)添加任何東西:
public class List<T> : ICollection<T>, IEnumerable<T>, IEnumerable, IList<T>, IReadOnlyCollection<T>, IReadOnlyList<T>, ICollection, IList
但事情發(fā)生變化的地方是使用 LINQ
。幾乎所有方法(我說(shuō)幾乎,但我認(rèn)為它是全部)都建立在 IEnumerable<T>
而不是 IEnumerable
之上。例如,LINQ
中的 Where
子句如下所示:
public static IEnumerable<TSource> Where<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate)
這意味著你不能在 ArrayList
上使用 LINQ
。在某些用例中,這沒(méi)什么大不了的,但你確實(shí)可以免費(fèi)獲得 LINQ
……所以選擇一個(gè)不支持它的類型真的是無(wú)緣無(wú)故地搬起石頭砸自己的腳。
性能影響
在某些情況下,在選擇 ArrayList
而不是 List<T>
時(shí)會(huì)對(duì)性能產(chǎn)生很大影響。這歸結(jié)為“裝箱”和“拆箱”的行為。簡(jiǎn)單來(lái)說(shuō),裝箱就是采用一個(gè)值類型(比如整數(shù))并將其包裝在一個(gè)對(duì)象中,并將其存儲(chǔ)在堆上而不是堆棧上。
但這對(duì) List
與 ArrayList
有何影響?當(dāng)我們?cè)?ArrayList
中存儲(chǔ)一個(gè)項(xiàng)目時(shí),它必須是對(duì)象類型(或類型),如果我們?cè)?ArrayList
中存儲(chǔ)一個(gè)值類型,那么在存儲(chǔ)它之前,它必須先“裝箱”對(duì)象并將其包裝起來(lái),List<int>
沒(méi)有相同的裝箱成本(盡管 List<object>
會(huì))。
為了對(duì)此進(jìn)行測(cè)試,我使用BenchmarkDotNet創(chuàng)建了一個(gè)基準(zhǔn)。BenchmarkDotNet可幫助你將方法轉(zhuǎn)化為基準(zhǔn)、跟蹤其性能并共享可重現(xiàn)的測(cè)量實(shí)驗(yàn)。參閱文章:
public class ArrayListVsListWrite
{
int itemCount = 10000000;
public ArrayList arrayList;
public List<int> list;
public List<object> listObject;
[IterationSetup]
public void Setup()
{
arrayList = new ArrayList();
list = new List<int>();
listObject = new List<object>();
}
[Benchmark]
public ArrayList WriteArrayList()
{
for(int i=0; i < itemCount; i++)
{
arrayList.Add(i);
}
return arrayList;
}
[Benchmark]
public List<object> WriteListObject()
{
for (int i = 0; i < itemCount; i++)
{
listObject.Add(i);
}
return listObject;
}
[Benchmark]
public List<int> WriteList()
{
for (int i = 0; i < itemCount; i++)
{
list.Add(i);
}
return list;
}
}
簡(jiǎn)單來(lái)說(shuō),我們正在循環(huán) 1000 萬(wàn)次并將項(xiàng)目??添加到列表中。結(jié)果是:
方法 | 平均 | 錯(cuò)誤 | 標(biāo)準(zhǔn)偏差 |
---|---|---|---|
WriteArrayList | 651.48 ms | 4.215 ms | 3.943 ms |
WriteListObject | 641.95 ms | 5.129 ms | 4.798 ms |
WriteList | 88.49 ms | 5.631 ms | 16.603 ms |
不難看出這里的性能差異。我們可以看到 List
類型的對(duì)象也存在同樣的裝箱/拆箱問(wèn)題。
在較小程度上,讀取也會(huì)變慢,因?yàn)槟銓⒆x取一個(gè)對(duì)象,然后“拆箱”該對(duì)象并將其轉(zhuǎn)換為整數(shù)。
什么時(shí)候應(yīng)該使用 ArrayList?
絕不。
唯一應(yīng)該使用 ArrayList
的時(shí)候是使用在 List<T>
之前構(gòu)建的庫(kù)時(shí),我相信它是在 .NET 2.0 中引入的。如果你使用的是面向 .NET 1.0 或 1.1 的庫(kù)(或構(gòu)建代碼),那么我猜 ArrayList
沒(méi)問(wèn)題。
相關(guān)文章