1. 程式人生 > >快速入門系列--CLR--03泛型集合

快速入門系列--CLR--03泛型集合

value mov nts readonly 只有一個 並且 cer view 工作

.NET中的泛型集合

在這裏主要介紹常見的泛型集合,很多時候其並發時的線程安全性常常令我們擔憂。因而簡述下.NET並發時線程安全特性,其詳情請見MSDN。

  • 普通集合都不支持多重並發寫操作
  • 部分支持單線程寫和並發讀操作
  • 同時.NET4添加了大量並發集合

首先介紹常見的泛型集合接口,其大部分都位於System.Collection.Generic命名空間。

  • IEnumerable<T>,其可以獲取一個IEnumerator<T>叠代器,如果從數據庫的角度來看,前者是表,後者是遊標,同時這兩個接口是唯一具有可變性的集合接口。
  • ICollection<T>,它擴展了IEnumerable<T>,添加了Count和IsReadOnly屬性,Add和Remove等操作方法,Contains等判定函數,所有的標準泛型集合都實現了該接口。
  • IList<T>,提供定位功能,包括一個索引器、Insert和RemoveAt,我們通常認為可以通過索引對該泛型集合進行隨機訪問。、
  • IDictionary<TKey, TValue>,表示鍵值對集合,擴展了ICollection<KeyValuePair<TKey, TValue>>,取值可以用TryXXX方式。
  • ISet<T>表示唯一值集,包含大量集合操作:交、並、補。

接下來介紹具體的集合泛型集合類型,在實際中需要根據具體場景選擇最適合的集合類型。

  • List<T>,其是列表的默認選擇,內含一個數組,並且提供列表的邏輯大小Count和後臺數組的大小Capacity,當數組滿了時,會進行擴容。由於是連續型的數據結構,其添加刪除操作的成本較高,提供二分查找,查找效率高。同時,其Sort操作會修改原始列表的內容,與OrderBy不同,並且Sort是不穩定的,會出現相等元素順序不同的情況。
  • 數組,最基礎的集合,均派生自System.Array,包括一維數組T[10],二維數組T[10, 20]等,通過Array類的靜態方法進行ConvertAll、FindAll和BinarySearch等操作。
  • Colletion<T>,位於System.Colletion.ObjectModel命名空間,為BindingList<T>和ObservableCollection<T>等擴展類型提供基類。與雙向綁定相關的集合類型,註意它們只會在包裝器發生變化發出通知,而基礎列表改變時不會引發任何事件。
  • ReadOnlyCollection<T>和ReadOnlyObservableCollection<T>,其也類似於包裝器,後者實現了INotifyCollectionChanged, INotifyPropertyChanged兩個接口。
  • Dictionary<TKey, TValue>,使用散列表,查找性能的優劣取決於散列函數的優劣,默認使用Equals和GetHashCode,可以通過制定IEqualityComparer<TKey>作為參數。
  • SortList<TKey, TValue>和SortedDictionary<TKey, TValue>,兩者都是字典類,前者內部維護一個排序的數組,添加刪除操作的事件復雜度為O(n),後者內部維護一個紅黑樹,添加刪除操作事件復雜度為O(log n),但會消耗更多的堆內存,使用IComparer<TKey>作比較。
  • HashSet<T>,是不含值的Dictionary<,>,具有相同性能特性,並且所維護順序一般與添加順序無關。
  • SortedSet<T>,是沒有值得SortedDictionary<,>,維護一個紅黑樹,添加刪除和檢查操作的事件復雜度為O(log n)。提供GetViewBetween方法返回介於原始集上下限之間的另一個SortedSet<T>,註意這是一個動態的視圖,會隨著原始集的改變而改變。盡管看起來很方便,但需要註意的是"天下沒有免費的午餐",為保持內部一致性,操作的代價更大。
  • Queue<T>,構建一個環形緩沖區,實際維護一個基礎數組,包含兩個索引,分別記住入隊和出隊的位置(Slot),如果入隊指針追上出隊指針,則進行擴容。提供Enqueue、Dequeue、Peek等方法進行入隊、出隊、查看操作。
  • Stack<T>,其實現更簡單,可以看做是一個提供Push、Pop、Peek操作的List<T>。

最後介紹並行集合,也就是線程安全的集合。(註意所有的並發類型都未實現IList<T>接口)

  • IProducerConsumerCollection<T>和BlockingCollection<T>,前者是生產者/消費者模型中數據存儲的抽象,後者是其包裝類,使用ConcurrentQueue<T>作為後臺存儲,提供ToArray方法獲得集合當前狀態快照,TryXXX方法允許有效的失敗模式減少對鎖的需求。(例如,當隊列中只有一個項時,兩個線程同時判斷它是否有項,並且都返回true,這是一個線程執行了出隊操作,而另外一個線程在執行出隊操作時,將拋出異常,因而需要對驗證隊列是否有項操作和有項就出隊操作作為一個整體,需要添加鎖)
  • ConcurrentBag<T>,ConcurrentQueue<T>,ConcurrentStack<T>,它們是對IProducerConsumerCollection<T>的實現,其GetEnumerator()方法返回集合快照,叠代時可以改變集合,但該改變不會反應到叠代器中。
  • ConcurrentDictionary<TKey, TValue>, 實現了IDictionary<TKey, TValue>接口。支持並發的讀寫和線程安全的叠代,但不同是,其在叠代過程中對字典的改變不能確定是否反應到叠代器上。

小節:在日常工作中,當遇到需要並發操作非集合類型的全局變量時,需要使用鎖來處理;而當是集合類型時,就需要使用對應的並行集合類來處理,其能很好的TPL協作在一起。尤其在使用非線程安全的字典類進行並發操作時,有時會出現死循環等情形,尤其需要註意。

Tip:where T:new()

參考文獻

  1. Jon, Skeet. 深入理解C#(第3版)[M]. 北京:人民郵電出版社, 2014. 469-483

快速入門系列--CLR--03泛型集合