目錄
CollectionBase/DictionaryBase的目的
一、C#集合類型概述
集合是.NET FCL(Framework Class Library)中很重要的一部分。所有的集合類都繼承自IEnumerator。集合類總體可分爲一下幾類:
- 關聯/非關聯型集合
- 順序/隨機訪問集合
- 順序/無序集合
- 泛型/非泛型集合
- 線程安全集合
各集合類接口關係圖
泛型與非泛型集合類的分析
泛型集合是類型安全的,基於固定的泛型T,運行時不需要像非泛型的執行Object和具體類型的類型轉換。泛型集合的效率相對較高。
兩者都能實現數據存儲,不同的是泛型只能存放T類型數據,有運行時檢測,而非泛型的都轉化爲Object存儲,能存儲任意類型,包括值類型,會帶來裝箱拆箱的性能損耗,同時都是Object類型(弱類型)編譯時無法類型檢測,運行時會導致類型不一致的安全性問題。
二、具體接口/類分析
CollectionBase/DictionaryBase的目的
- 都是抽象類,不能實例化;
- 目的是提供給用戶自定義實現強類型的集合,解決一般非泛型集合的弱類型不安全的問題。
IEnumerator/IEnumerable
IEnumerator定義了我們遍歷集合的基本方法,以便我們可以實現單向向前的訪問集合中的每一個元素。 所有的集合類都繼承了IEnumerator接口,包括String類。而IEnumerable只有一個方法GetEnumerator即得到遍歷器。
ICollection
從最上面第一張圖我們可以知道,ICollection是直接繼承自IEnumerable。而實際上也是如此,我們可以說ICollection比IEnumerable多支持一些功能,不僅僅只提供基本的遍歷功能,還包括:
- 統計集合和元素個數
- 獲取元素的下標
- 判斷是否存在
- 添加元素到未尾
- 移除元素等等。。。
ICollection 與ICollection<T> 略有不同,ICollection不提供編輯集合的功能,即Add和Remove。包括檢查元素是否存在Contains也不支持。
IList
IList則是直接繼承自ICollection和IEnumerable。所以它包括兩者的功能,並且支持根據下標訪問和添加元素。IndexOf, Insert, RemoveAt等等。我們可以這樣說,IEnumerable支持的功能最少,只有遍歷。而ICollection支持的功能稍微多一點,不僅有遍歷還有維護這個集合的功能。而IList是最全的版本。
IReadOnlyList
這個是在Framework4.5中新增的接口類型,可以被看作是IList的縮減版,去掉了所有可能更改這個集合的功能。比如:Add, RemoveAt等等。
IDictionary<TKey,TValue>
IDictionary提供了對鍵值對集合的訪問,也是繼承了ICollection<T>和IEnumerable,擴展了通過Key來訪問和操作數據的方法。
三、關聯性泛型集合類
關聯性集合類即我們常說的鍵--值對集合,允許我們通過Key來訪問和維護集合。我們先來看一下 FCL爲我們提供了哪些泛型的關聯性集合類:
- Dictionary<TKey,TValue>
- SortedDictionary<TKey,TValue>
- SortedList<TKey,TValue>
Dictionary<TKey,TValue>
是我們最常用的關聯性集合了,它的訪問,添加,刪除數據所花費的時間是所有集合類裏面最快的,因爲它內部用了Hashtable作爲存儲結構,所以不管存儲了多少鍵值對,查詢/添加/刪除所花費的時間都是一樣的,它的時間複雜度是O(1)。
Dictionary<TKey,TValue>優勢是查找插入速度快,那麼什麼是它的劣勢呢?因爲採用Hashtable作爲存儲結構,就意味着裏面的數據是無序排列的,所以想按一定的順序去遍歷Dictionary<TKey,TValue>裏面的數據是要費一點工夫的。
作爲TKey的類型必須實現GetHashCode()和Equals() 或者提供一個IEqualityComparer,否則操作可能會出現問題。
SortedDictionary<TKey,TValue>
SortedDictionary<TKey,TValue>和Dictionary<TKey,TValue>大致上是類似的,但是在實現方式上有一點點區別。SortedDictionary<TKey,TValue>用二叉樹作爲存儲結構的,並且按key的順序排列。
那麼這樣的話,SortedDictionary<TKey,TValue>的TKey就必須要實現IComparable<TKey>。如果想要快速查詢的同時又能很好的支持排序的話,那就使用SortedDictionary吧。
SortedList<TKey,TValue>
SortedList<TKey,TValue>是另一個支持排序的關聯性集合。但是不同的地方在於,SortedList實際是將數據存存儲在數組中的。也就是說添加和移除操作都是線性的,時間複雜度是O(n),因爲操作其中的元素可能導致所有的數據移動。但是因爲在查找的時候利用了二分搜索,所以查找的性能會好一些,時間複雜度是O(log n)。
SortedList< TKey,TValue>其內部維護的是數組,而SortedDictionary< TKey,TValue>內部維護的是紅黑樹(平衡二叉樹的一種),因此在佔用的內存和性能上,SortedList都好於SortedDictionary。
所以推薦使用場景是這樣地:如果你想要快速查找,又想集合按照key的順序排列,最後這個集合的操作(添加和移除)比較少的話,就是SortedList了。
四、非關聯性泛型集合類
非關聯性集合就是不用key操作的一些集合類,通常我們可以用元素本身或者下標來操作。FCL主要爲我們提供了以下幾種非關聯性的泛型集合類。
- List<T>
- LinkedList<T>
- HashSet<T>
- SortedSet<T>
- Stack<T>
- Queue<T>
List<T>
泛型的List 類提供了不限制長度的集合類型,List在內部維護了一定長度的數組(默認初始長度是4),當我們插入元素的長度超過4或者初始長度 的時候,會去重新創建一個新的數組,這個新數組的長度是初始長度的2倍(不永遠是2倍,當發現不斷的要擴充的時候,倍數會變大),然後把原來的數組拷貝過來。所以如果知道我們將要用這個集合裝多少個元素的話,可以在創建的時候指定初始值,這樣就避免了重複的創建新數組和拷貝值。
另外的話由於內部實質是一個數組,所以在List的末尾添加數據是比較快的,但是如果在數據的頭或者中間添加刪除數據相對來說更低效一些因爲會影響其它數據的重新排列。
LinkedList<T>
LinkedList在內部維護了一個雙向的鏈表,也就是說我們在LinkedList的任何位置添加或者刪除數據其性能都是很快的。因爲它不會導致其它元素的移動。一般情況下List已經夠我們使用了,但是如果對這個集合在中間的添加刪除操作非常頻繁的話,就建議使用LinkedList。
HashSet<T>
HashSet是一個無序的能夠保持唯一性的集合。我們也可以把HashSet看作是Dictionary<TKey,TValue>,只不過TKey和TValue都指向同一個對象。HashSet非常適合在我們需要保持集合內元素唯一性但又不需要按順序排列的時候。
HashSet不支持下標訪問。
SortedSet<T>
SortedSet和HashSet,就像SortedDictionary和Dictionary一樣,還記得這兩個的區別麼?SortedSet內部也是一個二叉樹,用來支持按順序的排列元素。
Stack<T>
堆棧:後進先出的隊列,不支持按下標訪問
Queue<T>
隊列:先進先出的隊列,不支持按下標訪問
五、推薦使用場景
非泛型類集合
泛型集合類是在.NET2.0的時候出來的,也就是說在1.0的時候是沒有這麼方便的東西的。現在基本上我們已經不使用這些集合類了,除非在做一些和老代碼保持兼容的工作的時候。來看看1.0時代的.NET程序員們都有哪些集合類可以用。
- ArraryList 後來被List<T>替代。
- Hashtable 後來被Dictionary<TKey,TValue>替代。
- Queue 後來被Queue<T>替代。
- SortedList 後來被SortedList<T>替代。
- Stack 後來被Stack<T>替代。
線程安全的集合類
並行集合的名字空間: System.Collections.Concurrent
- ConcurrentQueue 線程安全版本的Queue
- ConcurrentStack 線程安全版本的Stack
- ConcurrentBag 線程安全的對象集合
- ConcurrentDictionary 線程安全的Dictionary
- BlockingCollection 線程安全的支持界限和阻塞的容器
.NET爲我們提供的集合類是我們很常用的工具類之一,希望這篇文章能夠幫助大家更好的認識這些集合類。當然,個人感覺還有不完善的地方,比如說HashTable和Binary Search Tree就沒有細究下去,包括單向鏈表和雙向鏈表之間的對比本文也沒有提及。感興趣的朋友可以深入瞭解一下。
一個Dictionary操作實例:
using System;
using System.Collections;
using System.Collections.Generic;
namespace DictionaryDemo
{
class Program
{
static void Main(string[] args)
{
//定義
Dictionary<string, string> fileOperator = new Dictionary<string, string>();
//添加元素
fileOperator.Add("txt", "notepad.exe");
fileOperator.Add("bmp", "paint.exe");
fileOperator.Add("dib", "paint.exe");
fileOperator.Add("rtf", "wordpad.exe");
//取值
Console.WriteLine("For key = \"rtf\", value = {0}.", fileOperator["rtf"]);
//更改值
fileOperator["rtf"] = "winword.exe";
Console.WriteLine("For key = \"rtf\", value = {0}.", fileOperator["rtf"]);
//遍歷key
foreach (string key in fileOperator.Keys)
{
Console.WriteLine("Key = {0}", key);
}
//遍歷value
foreach (string value in fileOperator.Values)
{
Console.WriteLine("value = {0}", value);
}
//遍歷value, Second Method
Dictionary<string, string>.ValueCollection valueColl = fileOperator.Values;
foreach (string s in valueColl)
{
Console.WriteLine("Second Method, Value = {0}", s);
}
//遍歷字典
foreach (KeyValuePair<string, string> kvp in fileOperator)
{
Console.WriteLine("Key = {0}, Value = {1}", kvp.Key, kvp.Value);
}
//添加存在的元素
try
{
fileOperator.Add("txt", "winword.exe");
}
catch (ArgumentException)
{
Console.WriteLine("An element with Key = \"txt\" already exists.");
}
//刪除元素
fileOperator.Remove("doc");
if (!fileOperator.ContainsKey("doc"))
{
Console.WriteLine("Key \"doc\" is not found.");
}
//判斷鍵存在
if (fileOperator.ContainsKey("bmp")) // True
{
Console.WriteLine("An element with Key = \"bmp\" exists.");
}
//參數爲其它類型
Dictionary<int, string[]> OtherType = new Dictionary<int, string[]>();
OtherType.Add(1, "1,11,111".Split(','));
OtherType.Add(2, "2,22,222".Split(','));
Console.WriteLine(OtherType[1][2]);
//聲明並添加元素
Dictionary<int, DouCube> MyType = new Dictionary<int, DouCube>();
for (int i = 1; i <= 9; i++)
{
DouCube element = new DouCube();
element.Code = i * 100;
element.Page = "http://www.doucube.com/" + i.ToString() + ".html";
MyType.Add(i, element);
}
//遍歷元素
foreach (KeyValuePair<int, DouCube> kvp in MyType)
{
Console.WriteLine("Index {0} Code:{1} Page:{2}", kvp.Key, kvp.Value.Code, kvp.Value.Page);
}
}
}
//參數爲自定義類型
//首先定義類
class DouCube
{
public int Code { get { return _Code; } set { _Code = value; } } private int _Code;
public string Page { get { return _Page; } set { _Page = value; } } private string _Page;
}
}
六、其他參考資料
關於更多的C#集合類,還可以參考以下幾個帖子: