C#集體類型( Collections in C#)
集合是.NET FCL(Framework Class Library)中很重要的一部分,也是我們開發當中最常用到的功能之一,幾乎是無處不在。俗話說知其然,知其所以然,平常看到IEnumerable,IEnumerator,ICollection是不是知道他們之間各自的區別?除了List和Dictionary以外,你還用過哪些其它的集合類?廢話少說,今天我們就來看一些這些定義集合類的介面以及他們的實現。
集合介面
先來看一下,FCL為我們提供了哪些介面:
IEnumerable 和IEnumberator
public interface IEnumerator{ bool MoveNext(); object Current { get; } void Reset();}
IEnumerator定義了我們遍曆集合的基本方法,以便我們可以實現單向向前的訪問集合中的每一個元素。而IEnumerable只有一個方法GetEnumerator即得到遍曆器。
public interface IEnumerable{ IEnumerator GetEnumerator();}
注意:我們經常用的foreach即是一種文法糖,實際上還是調用Enumerator裡面的Current和MoveNext實現的遍曆功能。
List<string> list = new List<string>() { "Jesse", "Chloe", "Lei", "Jim", "XiaoJun"}; // Iterate the list by using foreachforeach (var buddy in list){ Console.WriteLine(buddy);} // Iterate the list by using enumeratorList<string>.Enumerator enumerator = list.GetEnumerator();while (enumerator.MoveNext()){ Console.WriteLine(enumerator.Current);}
上面的代碼中用到的foreach和enumerator到IL中最後都會被翻譯成enumerator的MoveNext和Current。
IEnumerable是一個很有用的介面,實現它的好處包括:
支援foreach語句
作為一個標準的集合類與其它類庫進行互動
滿足更複雜的集合介面的需求
支援集合初始化器
當然實現的方法也有很多,如下:
如果我們集合是通過封裝其它集合類而來的,那麼我們可以直接返回這個集合的enumerator
通過yield return 來返回
實現我們自己的IEnumerator來實現
這裡給大家示範一下如何通過yield來實現返回enumerator
public class BuddyList : IEnumerable{ private string[] data= new string[] { "Jesse", "Chloe", "Lei", "Jim", "XiaoJun" }; public IEnumerator GetEnumerator() { foreach (var str in data) { yield return str; } }} var myBuddies= new BuddyList();foreach (var str in myBuddies){ Console.WriteLine(str);}
ICollection<T>和ICollection
從最上面第一張圖我們可以知道,ICollection是直接繼承自IEnumerable。而實際上也是如此,我們可以說ICollection比IEnumerable多支援一些功能,不僅僅只提供基本的遍曆功能,還包括:
統計集合和元素個數
擷取元素的下標
判斷是否存在
添加元素到未尾
移除元素等等。。。
ICollection 與ICollection<T> 略有不同,ICollection不提供編輯集合的功能,即Add和Remove。包括檢查元素是否存在Contains也不支援。
IList<T>和IList
IList則是直接繼承自ICollection和IEnumerable。所以它包括兩者的功能,並且支援根據下標訪問和添加元素。IndexOf, Insert, RemoveAt等等。我們可以這樣說,IEnumerable支援的功能最少,只有遍曆。而ICollection支援的功能稍微多一點,不僅有遍曆還有維護這個集合的功能。而IList是最全的版本。
IReadOnlyList<T>
這個是在Framework4.5中新增的介面類型,可以被看作是IList<T>的縮減版,去掉了所有可能更改這個集合的功能。比如:Add, RemoveAt等等。
IDictionary<TKey,TValue>
IDictionary提供了對索引值對集合的訪問,也是繼承了ICollection<T>和IEnumerable,擴充了通過Key來訪問和操作資料的方法。
關聯性泛型集合類
關聯性集合類即我們常說的索引值對集合,允許我們通過Key來訪問和維護集合。我們先來看一下 FCL為我們提供了哪些泛型的關聯性集合類:
Dictionary<TKey,TValue>
SortedDictionary<TKey,TValue>
SortedList<TKey,TValue>
Dictionary<TKey,TValue>
Dictionary<TKey,TValue>可能是我們最常用的關聯性集合了,它的訪問,添加,刪除資料所花費的時間是所有集合類裡面最快的,因為它內部用了Hashtable作為儲存結構,所以不管儲存了多少索引值對,查詢/添加/刪除所花費的時間都是一樣的,它的時間複雜度是O(1)。
Dictionary<TKey,TValue>優勢是尋找插入速度快,那麼什麼是它的劣勢呢?因為採用Hashtable作為儲存結構,就意味著裡面的資料是無序排列的,所以想按一定的順序去遍曆Dictionary<TKey,TValue>裡面的資料是要費一點工夫的。
作為TKey的類型必須實現GetHashCode()和Equals() 或者提供一個IEqualityComparer,否則操作可能會出現問題。
SortedDictioanry<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)。所以推薦使用情境是這樣地:如果你想要快速尋找,又想集合按照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>
後進先出的隊列
不支援按下標訪問
Queu<T>
先進先出的隊列
不支援按下標訪問
推薦使用情境
集合 |
順序排列 |
連順儲存 |
直接存取方式 |
訪問時間 |
操作時間 |
備忘 |
Dictionary |
|
是 |
Key |
Key: O(1) |
O(1) |
訪問效能最快,不支援排序 |
SortedDinctionary |
順序排列 |
否 |
Key |
Key: O(log n) |
O(log n) |
快速存取和支援排序的折衷 |
SortedList |
順序排列 |
是 |
Key |
Key: O(log n) |
O(n) |
和SortedDictionary相似,只是內部用資料替代樹作為儲存結構。 |
List |
使用者可以精確控制元素的位置 |
是 |
Index |
Index: O(1) Value: O(n) |
O(n) |
最適合需要直接存取每一個元素的少量集合。 |
LinkedList |
使用者可以精確控制元素的位置 |
否 |
不支援 |
Value: O(n) |
O(1) |
最適合不需要直接存取單個元素,但是在集合中添加/移除非常頻繁的情境。 |
HashSet |
不支援 |
是 |
Key |
Key: O(1) |
O(1) |
能保持元素唯一性的集合。不支援排序 |
SortedSet |
順序排列 |
否 |
Key |
Key: O(log n) |
O(log n) |
能保持元素唯一性並且支援排序。 |
Stack |
LIFO |
是 |
只能擷取頂部元素 |
Top: O(1) |
O(1) |
|
Queue |
FIFO |
是 |
只能獲底部元素 |
Front: O(1) |
O(1) |
|
非泛型類集合
泛型集合類是在.NET2.0的時候出來的,也就是說在1.0的時候是沒有這麼方便的東西的。現在基本上我們已經不使用這些集合類了,除非在做一些和老代碼保持相容的工作的時候。來看看1.0時代的.NET程式員們都有哪些集合類可以用。
ArraryList
後來被List<T>替代。
HashTable 後來被Dictionary<TKey,TValue>替代。
Queue 後來被Queue<T>替代。
SortedList 後來被SortedList<T>替代。
Stack 後來被Stack<T>替代。
安全執行緒的集合類
ConcurrentQueue 安全執行緒版本的Queue
ConcurrentStack安全執行緒版本的Stack
ConcurrentBag安全執行緒的對象集合
ConcurrentDictionary安全執行緒的Dictionary
BlockingCollection
.NET為我們提供的集合類是我們很常用的工具類之一,希望這篇文章能夠協助大家更好的認識這些集合類。當然,個人感覺還有不完善的地方,比如說HashTable和Binary Search Tree就沒有細究下去,包括單向鏈表和雙向鏈表之間的對比本文也沒有提及。感興趣的朋友可以深入瞭解一下。