C#集合類型
C#集體類型( Collections in C#)
集合是.NET FCL(Framework Class Library)中很重要的一部分,也是我們開發當中最常用到的功能之一,幾乎是無處不在。俗話說知其然,知其所以然,平常看到IEnumerable,IEnumerator,ICollection是不是知道他們之間各自的區別?除了List和Dictionary以外,你還用過哪些其它的集合類?廢話少說,今天我們就來看一些這些定義集合類的接口以及他們的實現。
- 集合接口
- 關聯性泛型集合類
- 非關聯性泛型集合類
- 推薦使用場景
- 非泛型集合類
- 線程安全集合類
集合接口
先來看一下,FCL為我們提供了哪些接口:
IEnumerable 和IEnumberator
1 2 3 4 5 6 7 |
public interface IEnumerator
{
bool MoveNext();
object Current { get ; }
void Reset();
}
|
IEnumerator定義了我們遍歷集合的基本方法,以便我們可以實現單向向前的訪問集合中的每一個元素。而IEnumerable只有一個方法GetEnumerator即得到遍歷器。
1 2 3 4 |
public interface IEnumerable
{
IEnumerator GetEnumerator();
}
|
註意:我們經常用的foreach即是一種語法糖,實際上還是調用Enumerator裏面的Current和MoveNext實現的遍歷功能。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
List< string > list = new List< string >()
{
"Jesse" ,
"Chloe" ,
"Lei" ,
"Jim" ,
"XiaoJun"
};
// Iterate the list by using foreach
foreach ( var buddy in list)
{
Console.WriteLine(buddy);
}
// Iterate the list by using enumerator
List< 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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
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) |
快速訪問和支持排序的折衷 |
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就沒有細究下去,包括單向鏈表和雙向鏈表之間的對比本文也沒有提及。感興趣的朋友可以深入了解一下。
C#集合類型