簡單的介紹一下集合,通俗來講就是用來保管多個數據的方案。比如說我們是一個公司的倉庫管理,公司有一堆貨物需要管理,有同類的,有不同類的,總而言之就是很多、很亂。我們對照集合的概念對倉庫進行管理的話,那麼 數組就是將一堆貨整整齊齊的碼在倉庫的某個地方,普通列表也是如此;Set就是在倉庫裡有這麼一個貨架,每種貨品只能放一個,一旦某種貨品超過一個了貨架就塌了;Dictionary字典呢,在一個貨架上隨機擺放,然後再找一個本子把每個貨品存放的位置記錄下來。
1. 主要集合
C#/.NET Framework 提供了很多很有意思的集合類,數組、列表、鍊表、Set、字典等一系列的類。其中數組是語言的一部分,個人認為嚴格意義上不屬於集合類這一部分。C#開發中常用的集合有數組、 List類、Set接口、Dictionary類、Queue類、LinkedList類等,其他的出鏡率不高。 與其他(java)語言不同的一點是,C#的List是類,而不是接口,接口是IList,但這個接口意義不大,在使用IList的時候更多的傾向於使用IEnumerable,這主要是因為IEnumerable有Linq的支持,再者兩者的方法基本一致,能用IList的地方基本都可以用IEnumerable。
1.1 Array 數組
數組,集合的基礎部分,主要特點是一經初始化就無法再次對數組本身進行增刪元素。C#雖然添加了一些修改數組的擴展方法,但基本都會返回新的數組對象。
1.1.1 初始化
數組的初始化需要指定大小,可以顯示指定或者隱式的指定。
// 顯示指定類型與大小,具體的元素後續賦值string[] strArr = newstring[10];//指定類型同時給元素賦值,具體大小由編譯器自動推斷string[] strArr1 = newstring[]{"1","2","3","4","5","6","7","8","9","10"};// 類型和大小都由編譯器進行推斷string[] strArr2 = new []{"1","2","3","4","5","6","7","8","9","10"};
1.1.2 常用方法
訪問和賦值 數組可以通過下標訪問數組中的元素,下標從0開始,表示0位。代碼如下:string item0 = strArr[0]; //取出 "1"string item2 = strArr[2]; // 取出 "3"strArr[0] = "3"; // strArr = {"3","2","3","4","5","6","7","8","9","10"}
獲取長度int length = strArr.Length;// 獲取一個整型的長度
//獲取一個長整型的長度,對於一個非常大的數組且長度可能會超過int的最大值
long longLength = strArr.LongLength;
循環迭代// 普通for 循環for(int i = 0;i < strArr.Length;i++){string it = strArr[i];}// foreach 循環foreach(string it in strArr){// 依次循環,不需要下標,操作更快一點}
1.1.3 不常用但有用的方法
CopyTo複製到publicvoidCopyTo(Array array, int index);publicvoidCopyTo(Array array, long index);參數說明: array 需要複製到的數組,index 目標數組的起始下標方法說明:將 源數組的元素依次複製到 array從index下標開始的位置string[] strArr1 = newstring[]{"1","2","3","4","5","6","7","8","9","10"}; string[] strArr3 = newstring[10];strArr1.CopyTo(strArr3, 0); //strArr3 = {"1","2","3","4",'5","6","7","8","9","10"}值得注意的是strArr3的長度不能 小於 index + strArr1.LengthSort排序這個方法不是數組對象的方法,而是Array提供的一個靜態方法。int[] arr1 = new[] {1, 9, 28, 5, 3, 6, 0, 12, 44, 98, 4, 2, 13, 18, 81, 92}; Array.Sort(arr1);//0,1,2,3,4,5,6,9,12,13,18,28,44,81,92,98值得注意的是,該方法是直接對數組進行操作,所以不會返回新的數組。ToList 轉成List顧名思義,將Array對象轉成List對象。這裡需要額外注意的是,轉換成的List是不可改變長度的。Clone() 獲得一個淺拷貝的數組對象獲取該對象的一個淺拷貝數組對象。
至於其他的Array類和Array對象 還有很多有意思的方法,但是平時開發的時候使用的頻率比較低。這裡就不一一介紹了,以後需要會介紹一下的。
1.2 List 列表
List列表為一個泛型類,泛型表示<T>,其中T表示列表中存放的元素類型,T代表C#中可實例化的類型。關於泛型的具體描述以後介紹,現在回過頭來繼續介紹列表。列表內部持有一個數組對象,列表有兩個私有變量:一個是列表容量,即內部數組的大小;另一個是存放的元素數量,通過Count獲取。
List列表通過元素數量實現了Add和Remove的操作,列表對象操作引發元素數量變動時都會導致對容量的重新計算,如果現有容量不滿足後續操作需要的話,將會對現有數組進行擴充。
1.2.1 初始化
List<string> list = new List<string>();// 初始化一個空的列表//初始化一個包含兩個元素的列表List<string> list1 = new List<string>{"12", "2"};//初始化一個空的列表,並指定list的初始容量為100list1 = new List<string>(100);list = new List<string>(list1);// 使用一個List/Array 初始化一個列表
1.2.2 常用方法
Count或LongCount 獲取元素的數量Count 表示獲取一個int類型的的數量值,LongCount表示獲取一個long類型的數量值。通常情況下兩者返回的結果是一致的,但是如果列表中元素的數量超過了int允許的最大返回直接使用Count獲取將會出現數據溢出的問題,這時候就需要LongCount了。訪問元素/修改元素C#的列表操作單個元素很簡單 ,與數組的操作方式完全一樣。string str = list1[0];//獲取 list1 的第一個元素,即下標為0的元素list1[2] = "233"; // 將 list1 的第三個元素設置為「233」 ,即下標為2 的元素,這裡假設list1有至少三個元素需要注意的地方是,如果給定的下標超過了List對象的索引值範圍會報ArgumentOutOfRangeException。判斷方法就是 下標>=Count,如果滿足就會越界。Add或AddRange 添加到列表最後將元素添加到List的末尾,Add添加一個,AddRange添加一組,支持數組、列表。List<string> list = new List<string>();// 初始化一個空的列表list.Add("12");//list = {"12"}List<string> list1 = new List<string>{"14", "2"};list.AddRange(list1);// list = {"12","14","2"}Insert(int index, T item)或InsertRange(int index,IEnumerable<T> items)插入Insert(int index,T item)在 index 下標處插入一個元素,該下標以及該下標以後的元素依次後移InsertRange(int index,IEnumerable<T> items)在index下標處插入一組元素,該下標以及之後的元素依次後移示例:List<int> arr1 = new List<int>{1, 9, 28, 5, 3, 6, 0, 12, 44, 98, 4, 2, 13, 18, 81, 92};arr1.Insert(3,37);// arr1 = 1,9,28,37,5,3,6,0,12,44,98,4,2,13,18,81,92下標為3的元素變成了37,之後的元素依次後移了List<int> arr1 = new List<int>{1, 9, 28, 5, 3, 6, 0, 12, 44, 98, 4, 2, 13, 18, 81, 92};List<int> arr2 = new List<int>{2,3,4,5};arr1.InsertRange(2,arr2);//arr1= 1,9,2,3,4,5,28,5,3,6,0,12,44,98,4,2,13,18,81,92 可以明顯發現下標為2的元素發生了變化Contains(T item)是否包含返回一個Boolean類型的結果,如果包含則返回true,如果不包含則返回falseList<int> arr2 = new List<int>{2,3,4,5}; arr2.Contains(8);//falsearr2.Contains(3);//trueRemove(T item) 刪除指定元素List<int> arr2 = new List<int>{2,3,4,5}; arr2.Remove(3);// arr2 = 2,4,5arr2.Remove(6);//arr2 = 2,4,5值得注意的是,如果刪除一個不存在的元素時,不會報錯,列表也不會發生任何改變。RemoveAt(int index)刪除位於下標的元素List<int> arr2 = new List<int>{2,3,4,5}; arr2.RemoveAt(1);//arr2 = 2,4,5如果移除的下標超過了列表的最後一個元素的下標將會拋出異常RemoveRane(IEnumerable<T> items)刪除一組元素與Remove(T item)一致,如果要刪除的元素不在列表中,則列表元素不會發生變化。List<int> arr1 = new List<int>{1, 9, 28, 5, 3, 6, 0, 12, 44, 98, 4, 2, 13, 18, 81, 92}; List<int> arr2 = new List<int>{2,3,4,5}; arr1.RemoveRange(arr2);GetRange(int index,int count)從列表中獲取一個子列表,從index開始,獲取count個元素,如果源列表中從index開始剩餘的元素不足count個將會報錯。
1.2.3 不常用但有用的方法
Clear()刪除所有元素將列表清空,調用方法之後,列表中將不包含任何元素Reverse()調轉順序將列表按照從尾到頭的順序進行排列IndexOf(T item)查找下標查找元素在列表中的下標,如果沒找到元素,則返回-1Sort()排序對列表進行排序,調用方法後,會按照默認排序方法返回一個排序結果
1.3 Set 集合
C#沒有為Set單獨設置類,一方面是因為Set出鏡率不高,另一方面也因為Set本身的機制所致。Set集合不能包含重複元素,如果嘗試存入重複元素集合元素將不會發生任何變化。 Set集合中元素的順序與存放順序不一定相同。因為Set集合中存放對於使用者而言是亂序存放的。 我們常用的Set集合有HashSet<T>和SortSet<T>,其他的Set相關類則屬於更加少見。至少在我5年多的開發經歷中沒有用過。
1.3.1 HashSet<T>和SortSet<T>
HashSet俗稱 哈希集合或者哈希Set,內部使用Hash值作為元素的唯一性驗證,即調用對象的HashCode()方法作為Hash值的來源。SortSet顧名思義,排序集合,它每次在插入的時候都會對元素進行一次排序
1.3.2 共同點
初始化兩者相同的地方就是 都有以下幾種初始化方法Set<T>set = new HashSet<T>();// = new SortSet<T>(); 初始化一個空的集合//使用一個集合對象初始化Set<T> set1 = new HashSet<T>(IEnumerable<T> items);// = new SortSet<T>(IEnumerable<T> items);Set<T> set2 = new HashSet<T>(){T t1, T t2, T t3};// 與上一種一樣添加元素set1.Add(item);// 集合只支持添加單個元素,但是可以通過集合運算的方式增加多個元素移除元素set1.Remove(item);//刪除集合中與item判斷相等的元素訪問元素需要注意的地方是,C#對Set沒有支持下標訪問方式獲取Set裡的元素,這是因為索引位置對於集合來說意義不大,沒有操作意義。foreach (var item in set1) { // 操作 }Set 只能通過遍歷訪問元素,不能通過Get或者下標操作訪問元素。關於foreach循環會在下一篇《C#基礎知識系列》裡進行介紹。集合運算
UnionWith並SortedSet<int> set = new SortedSet<int>{1,0,29,38,33,48,17}; set.UnionWith(new []{5,57,8,4,3,1,0,33});// set = 0,1,3,4,5,8,17,29,33,38,48,57通過傳入一個集合對象,將該集合設置為兩個集合的併集,也就是說取上圖 A,B,C 三個區域的和ExceptWith差SortedSet<int> set = new SortedSet<int>{1,0,29,38,33,48,17}; set.ExceptWith(new []{5,57,8,4,3,1,0,33}); // set =17,29,38,48傳入一個集合,從set中去掉同屬於兩個集合的元素,保留只存在於set的元素,也就是取上圖中的A部分元素IntersectWith交SortedSet<int> set = new SortedSet<int>{1,0,29,38,33,48,17}; set.ExceptWith(new []{5,57,8,4,3,1,0,33}); // set =0,1,33傳入一個集合,保留set與傳入集合裡相同的元素,也就是說取的是上圖中的B部分SymmetricExceptWith餘集SortedSet<int> set = new SortedSet<int>{1,0,29,38,33,48,17}; set.SymmetricExceptWith(new []{5,57,8,4,3,1,0,33});//set= 3,4,5,8,17,29,38,48,57傳入一個集合,保留set與傳入集合兩個集合中不同的元素,也就是取上圖的A+C這兩部分。Contains包含判斷集合中是否包含目標元素,返回true/falseSortedSet<int> set = new SortedSet<int>{1,0,29,38,33,48,17}; set.Contains(1);// true
1.3.3 不同點
初始化HashSet<T>支持傳入一個自定義的相等比較器,該比較器需要返回一個 bool值;可以指定起始容量SortSet<T>支持傳入一個自定義的大小比較器,該比較器返回一個int值;不能指定起始容量其他Comparer屬性:SortSet 可以獲取大小比較器;HashSet 獲取一個相等比較器
1.4 Dictionary 字典
Dictionary字典,正如它的名稱一樣,Dictionary需要指定兩個類型,一個作為索引鍵,一個作為數據值。就像字典一樣,每一個詞條內容都只有一個字詞索引,但可以出現同義詞一樣。當然,作為我博大精深的中文會出現同字不同音的詞組,但是一旦把音、字組合起來作為索引,那還是只會出現一個詞條。 所以Dictionary的使用方式也跟字典一樣,通過索引訪問和操作數據。
1.4.1 初始化
Dictionary的初始化有如下幾個方法:
Dictionary<string, int> dict = new Dictionary<string, int>();// 鍵是字符串,值是int類型Dictionary<string,int> dict1 = new Dictionary<string, int>(10);// 指定初始容量是10// 在大括號標記中 通過 {key,value}的寫法創建一個 字典對象,並包含這些鍵值對Dictionary<string,int> dict2 = new Dictionary<string, int>() { {"1",1}, {"2",2} };// 傳入一個字典對象,以傳入的對象為基礎創建一個字典Dictionary<string,int> dict3 = new Dictionary<string, int>(dict2);
1.4.2 常用方法
添加元素Dictionary<string, int> dict = new Dictionary<string, int>(); // 方法一dict.Add("1",2);//添加一個 鍵為「1」,值為2的鍵值對。//方法二//字典可以類似列表的形式通過下標添加或更新鍵對應的值,//不過與列表不同的是,字典的下標是字符串 dict["2"] = 4;// 如果 dict中2有值,則更新為4,如果沒有,則設置2對應的值為4獲取元素Dictionary<string, int> dict = new Dictionary<string, int>(); /* 省略數據填充階段 */intvalue = dict["2"]; // value = 4// 如果Dictionary中不存在索引為「2」的數據// 將會拋出 System.Collections.Generic.KeyNotFoundException 異常C# 的Dictionary還有一個TryGetValue方法可以用來嘗試獲取,他的使用方法是這樣的:int obj = 0; boolean isContains = dict.TryGetValue("3", out obj); // 方法會返回 dict是否包含鍵「3」的結果,如果有 obj 則存放了dict中對應的值,如果沒有,則返回false且不改變 obj 的值Count 獲取Dictionary裡鍵值對的數量。intcount = dict.Count;Dictionary沒有LongCount屬性,因為對於Dictionary存放數據需要比對Key的相等性,如果存放巨量數據將會對數據的訪問和操作效率有影響。Keys 獲取Dictionary裡所有的鍵,返回一個KeyCollection對象,不需要關心這是一個什麼類型,可以簡單的把它當做一個存放了鍵的HashSet。ContainsKey() 是否包含鍵:通常與獲取元素一起使用,可以先判斷Dictionary裡是否有這個鍵,然後再進行後續操作。Remove() 刪除Dictionary中鍵對應的元素,刪除後再次訪問會報錯。如果刪除一個不存在的元素將返回flase。 操作示例:Dictionary<string,int> dict = new Dictionary<string, int>(); //省略賦值操作bool result = dict.Remove("2");// 如果dict裡包含鍵為「2」的元素,則result為true,否則為false另一種方法:intvalue = 0; bool result = dict.Remove("2", outvalue); // 如果dict 裡包含鍵為「2」的元素,則result 為 false且value為對應的值
1.4.3 不常用但有用的方法
ContainsValue()是否包含值,與ContainsKey的用法一樣,只不過遍歷的是值;用處不大。Values獲取值的集合類似與KeyValues。
2. 傳統集合(非泛型)
C#的傳統集合基本都存放在System.Collections命名空間裡,詳細的可以查看微軟官方文檔。這個命名空間裡的集合類使用都不多,不過C#的集合體系的接口規範都是在這個裡面定義的。 2.1 常見類介紹
ArrayListList的非泛型版,與List操作方法一致,不過返回值是Object類型SortedList一個排序的鍵值對集合。雖然C#框架保留了非泛型集合元素,但不建議使用非泛型集合進行開發。
3 一些不常用的集合類
除了之前所說的幾個集合類,C#還設置了一些在開發中不常用但在特定場合很有用的集合類。
3.1Queue<T>和Queue
這兩個類是一對的,一個是泛型類,一個是非泛型類。該類中文名稱是隊列,如其名,隊列講究一個先進先出,所以隊列每次取元素都是從頭取,存放是放到隊列尾。 操作代碼如下:
加入隊列Queue queue = new Queue(); queue.Enqueue(1); queue.Enqueue("2"); Queue<string> queue1 = new Queue<string>(); queue1.Enqueue("stri");//讀取隊首的元素 讀取有兩種:讀取但不移除元素:object obj= queue.Peek(); string str = queue.Peek();讀取並移除元素:object obj = queue.Dequeue(); string str = queue.Dequeue();Count 獲取元素數量
3.2 LinkedList<T>
LinkedList,鍊表。與List不同的地方是,LinkedList的元素是LinkedListNode對象,該對象有四個屬性,分別是List指向列表對象,Previous指向前一個對象如果有的話,Next指向後一個對象如果有的話。所以根據元素的屬性可以發現鍊表的工作方式,鍊表就像一條鎖鏈一樣,一個元素分三塊,一個指向前一個元素,一個用來存放值,一個指向下一個元素,簡單如下圖所示:
所以可以明顯的發現LinkedList在隨機插取上比一般的要快,因為它不用維護一個數組,但是在查找和坐標操作上明顯要慢很多。
LinkedList簡單介紹這麼多,可以看看它的一些常見操作:
First 第一個元素獲取第一個元素Last 最後一個元素獲取最後一個元素AddAfter/AddBefore在某個節點後/在某個節點前插入數據 支持以下參數列表:(LinkedListNode<T> node, T value)(LinkedListNode<T> node, LinkedListNode<T> newNode)第一個參數表示要插入的節點位置,第二個表示要插入的節點/元素。第一個參數會校驗是否屬於該鍊表,如果不屬於則會拋出一個異常。第二個可以是值,也可以是初始化好的節點對象。如果是節點對象,則判斷是否歸屬其他鍊表,如果是其他鍊表拋出異常。AddFirst/AddLast添加元素到頭或者尾,可以使用LinkedListNode或者添加值。Remove刪除,可以傳遞某個節點,或者要刪除的節點裡存放的值。RemoveFirst/RemoveLast刪除第一個節點,刪除最後一個節點,不含參數
3.3 Stack<T> 和 Stack
Stack廣泛的翻譯是棧,是一種後進先出的集合。在一些特殊場景裡,使用十分廣泛。
Stack有兩個很重要的方法Pop 和Push,出/進。
Pop 獲取最後一個元素,並退出棧,Push 向棧推入一個元素。 具體可以參照官方文檔
4 集合相關命名空間
C# 的集合還有其他的一些命名空間裡藏著寶貝,不過在實際開發中使用頻率並不大,可以按需查看。
4.1 System.Collections.Concurrent
線程安全 這個命名空間,提供了一系列線程安全的集合類,當出現多線程操作集合的時候,應當使用這個命名空間的集合。名稱和常用的類是一一對應的,不過只提供了ConcurrentDictionary<TKey,TValue>、ConcurrentQueue<T>、ConcurrentStack<T>等幾個集合類。具體可以查看官方文檔
4.2 System.Collections.Immutable
不可變集合 命名空間包含用於定義不可變集合的接口和類,如果需要使用這個命名空間,則需要使用NuGet下載。
共享集合,使其使用者可以確保集合永遠不會發生更改。提供多線程應用程式中的隱式線程安全(無需鎖來訪問集合)。遵循函數編程做法。在枚舉過程中修改集合,同時確保該原始集合不會更改。