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