C#集合類(資料結構)
一、選擇資料結構
1)線性容器List<T>陣列/Stack/Dequeue按需求模型選擇即可,LinkedList<T>是雙向連結串列增刪修改快.
需要有序陣列SortList<T>線性排序容器都可以;如果既需要查詢快又需要頻繁修改那麼可以用List<T>記錄索引,用LinkedList<T>儲存。
2)二叉樹型別容器
SortedDictionary<TKey,TValue>可以提供二叉樹型別插入刪除查詢都比較折中的鍵值對容器。
SortedSet<T>一個集合值型別的容器,比SortedDictionary<TKey,TValue>需要更少的空間。
3)雜湊表型別的容器
Dictionary<TKey,TValue>類似於C++/java中的HashMap實現,需要一個雜湊函式和一個相等判斷函式解決衝突,能夠有很高的插入和查詢效率。
HashSet<T>適合單個元素的集合操作型別。
ILookup<TKey,TValue>可以獲得一個鍵對應多個值的儲存型別,很有用的方面是從指定集合中篩選某種型別的資料集。
4)其它支援容器的介面類,委託,拓展方法和為了觀察,封裝位操作,封裝多執行緒操作的衍生型別容器
其它功能型別的介面及其委託,拓展方法:
ICollection<T>、IEnumerable<T>、IEquatable<T>、IComparer<T>、IComparable<T>、IEqualityComparer<T>、IFormattable介面
為了觀察,封裝位操作,封裝多執行緒操作的衍生型別容器:
ObservableCollection<T>,BitArray/BitVector32、IProducerConsumerCollection<T>介面
大多數集合類都在System.Collections.Generic名稱空間中,非泛型的System.Collections中已經很少用了。
特定集合類位於System.Collections.Specialized中,執行緒安全的集合類在System.Collections.Concurrent中。
集合類主要有:
二.ICollection<T>、IEnumerable<T>、IEquatable<T>、IComparer<T>、IComparable<T>、IEqualityComparer<T>、IFormattable介面。
三.Array、List<T>、佇列、棧、SortedList<TKey, TValue>、LinkedList<T>雙向連結串列
1.List<T>
List<T>在C#中實現也是陣列,動態陣列長度不夠會加倍。不確定的陣列需要可變陣列用List<T>, 確定長度和數量多用Array, 不推薦用ArrayList因為新增的是object型別要裝箱和拆箱效能慢。
1)初始化:
初始化時候可以直接賦值,或者指定Count和Capacity來初始化。
對List<T>填充完資料以後可以用TrimExcess()方法去除不需要的容量,只有空白容量超過10%才會去除成功。
List<T>可以用AddRange新增多個元素。
2)訪問:
可以通過索引器訪問的集合類有:Array,List<T>,ArrayList, StringCollection, List<T>。
List<T>實現了IEnumerable<T>介面,所以也可以用foreach來訪問。
List<T>提供了ForEach方法,該方法用Action<T>作為引數。
public void ForEach(Action<T> action);
// 需要當前類中定義該委託的例項賦值給Action委託物件,也可以用Lambda表示式宣告該例項。
public delegate void Action<T> (T obj);
3)刪除
用RemoveAt效率較快,如果用Remove回先查詢值然後刪除回查詢引用,如果有重寫IEquatable<T>或者Object.Equals就會用這些方法判斷,否則
是用預設的引用比較,如果是相同的引用地址那麼就可以刪除成功。
刪除還可以用RemoveRange(index, count)來進行刪除。
如果要刪除指定特性的元素就可以用RemoveAll()方法,指定特性在Predicate<T>引數中指定。
要直接刪除所有的元素用ICollection<T>介面中定義的Clear()方法。
4)搜尋
IndexOf(),LastIndexOf(), FindIndex(), FindLastIndex(), Find(),FindLast().
判斷存在用Exists();
FindIndex()方法需要一個Predicate型別的引數。
public int FindIndex(Predicate<T> match);
需要給委託物件傳遞一個宣告的委託例項,例如:
public bool FindCountryPredicate(Racer racer)
{
if( racer == null) return false;
return racer.Country == country;
}
將FindCountryPredicate傳入函式引數,即可,RemoveAll()中也需要傳遞入該委託例項。
Find(), FindIndex(), FindAll()都需要這樣的比較委託例項,委託例項的廣泛使用,如果只寫一次可以用Lambd表示式來寫,多次將其封裝為函式。
5)排序
排序Sort方法也需要傳遞比較大小的委託例項。
有大概三種比較大小的委託函式:
預設的是IComparable<T>一個other引數的比較委託。
IComparer<T>兩個引數的比較委託。
過載Sort方法,該方法需要一個Comparison<T>的委託例項。Comparison<T>的委託定義是public delegate int Comparison<T>(T x, T y);
可以呼叫Reverse()方法逆轉整個集合的排序。
6)型別轉換
使用List<T>類的ConvertAll<TOutput>()方法,可以把所有的集合型別轉換為另一種型別。
該TOutput委託的定義如下:
public sealed delegate TOutput Convert<TInput, TOutput>(TInput from);
需要定義一個委託例項,傳入該函式引數即可。
7)只讀集合
一般集合都是要支援讀寫的,但是有些比較特殊的應用需要給客戶提供一個只讀集合,那麼可以使用List<T>集合的AsReadOnly()方法就可以返回
一個ReadOnlyCollection<T>型別的物件。ReadOnlyCollection<T>和List<T>的差別只是不能寫排序刪除等,其它實現都一樣。
List簡單例子:
static void Main()
{
var graham = new Racer(7, "Graham", "Hill", "UK", 14);
var emerson = new Racer(13, "Emerson", "Fittipaldi", "Brazil", 14);
var mario = new Racer(16, "Mario", "Andretti", "USA", 12);
var racers = new List<Racer>(20) { graham, emerson, mario };
racers.Add(new Racer(24, "Michael", "Schumacher", "Germany", 91));
racers.Add(new Racer(27, "Mika", "Hakkinen", "Finland", 20));
racers.AddRange(new Racer[] {
new Racer(14, "Niki", "Lauda", "Austria", 25),
new Racer(21, "Alain", "Prost", "France", 51)});
var racers2 = new List<Racer>(new Racer[] {
new Racer(12, "Jochen", "Rindt", "Austria", 6),
new Racer(22, "Ayrton", "Senna", "Brazil", 41) });
Console.WriteLine("-------racers------------");
for( int i = 0; i < racers.Count; i++ )
{
Console.WriteLine(racers[i].ToString());
}
Console.WriteLine("-------racers2------------");
for (int i = 0; i < racers2.Count; i++)
{
Console.WriteLine(racers2[i].ToString());
}
}
2.Queue<T>
先進先出,實現了ICollection和IEnumerable<T>介面,但沒有實現ICollection<T>介面,因此這個介面定義的Add()和Remove()方法不能用於佇列。
沒有實現List<T>介面,所以也不支援索引器訪問。
佇列中的常用方法,Count返回個數,Dequeue()進佇列,Enqueue出佇列並刪除佇列頭元素,Peek從佇列頭部讀取佇列但不刪除元素。
TrimExcess()可以清除Capacity中的大於10%時候的元素。
佇列Queue<T>的構造預設會4,8,16,32遞增的增加容量,.net 1.0版本的Queue卻是一開始就給了個32項的空陣列。
佇列例子:
public class DocumentManager
{
// readonly只是說這個佇列物件不寫(比如另一個物件拷貝給它),但是內部的元素是可以寫的
private readonly Queue<Document> documentQueue = new Queue<Document>();
public void AddDocument(Document doc)
{
lock (this)
{
documentQueue.Enqueue(doc);
}
}
public Document GetDocument()
{
Document doc = null;
lock (this)
{
doc = documentQueue.Dequeue();
}
return doc;
}
public bool IsDocumentAvailable
{
get
{
return documentQueue.Count > 0;
}
}
}
using System;
using System.Threading;
namespace Wrox.ProCSharp.Collections
{
public class ProcessDocuments
{
private DocumentManager documentManager;
protected ProcessDocuments(DocumentManager dm)
{
documentManager = dm;
}
public static void Start(DocumentManager dm)
{
// ParameterizedThreadStart;
// public delegate void ParameterizedThreadStart(object obj);
// 直接這樣啟動一個執行緒了。
new Thread( new ProcessDocuments(dm).Run ).Start();
}
protected void Run(object obj) // 這裡用不用object obj引數都可以,IL會做轉換為友object的。
{
while (true)
{
if (documentManager.IsDocumentAvailable)
{
Document doc = documentManager.GetDocument();
Console.WriteLine("Processing document {0}", doc.Title);
}
Thread.Sleep(new Random().Next(20));
}
}
}
}
using System;
using System.Threading;
namespace Wrox.ProCSharp.Collections
{
class Program
{
static void Main()
{
var dm = new DocumentManager();
ProcessDocuments.Start(dm);
// Create documents and add them to the DocumentManager
for (int i = 0; i < 1000; i++)
{
Document doc = new Document("Doc " + i.ToString(), "content");
dm.AddDocument(doc);
Console.WriteLine("Added document {0}", doc.Title);
Thread.Sleep(new Random().Next(20));
}
}
}
}
3.Stack<T>
後進先出,Count屬性,Push(),Pop()方法會刪除最頂元素,Peek()不會刪除,Contains()確定某個元素是否在棧中是則返回true.
using System;
using System.Collections.Generic;
namespace Wrox.ProCSharp.Collections
{
class Program
{
static void Main()
{
var alphabet = new Stack<char>();
alphabet.Push('A');
alphabet.Push('B');
alphabet.Push('C');
Console.Write("First iteration: ");
// 迭代遍歷用了迭代模式,不會刪除
foreach (char item in alphabet)
{
Console.Write(item);
}
Console.Write("Second iteration: ");
while (alphabet.Count > 0)
{
// Pop會刪除
Console.Write(alphabet.Pop());
}
Console.WriteLine();
}
}
}
4.SortedList<TKey, TValue>
實現是基於陣列的列表,定義了單一任意型別的鍵和單一任意型別的值的資料結構,可以直接建立一個空的排序列表;或者過載建構函式可以定義列表容量和傳遞一個IComparer<TKey>介面的物件,該介面用於給列表中的元素排序。
為容器新增元素可以用Add()方法,也可以用索引下標賦值,相同鍵新增時候Add方法會丟擲異常不能覆蓋舊鍵,索引下標相同鍵時候會覆蓋舊鍵不拋異常。
訪問時候可以用集合迭代器元素的Key,Value屬性訪問鍵和值;也可以用集合的Values和Keys屬性來返回所有的鍵值屬性,類似C++中的STL map一樣。通過索引器鍵值訪問元素時候,如果鍵不存在,那麼會丟擲異常,為了避免異常發生,可以用ContiansKey方法來判斷是否存在集合中,再用索引器訪問。
還可以直接用TryGetValue方法,嘗試獲得該鍵的值,如果不存在也不會丟擲異常。
例項:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace Wrox.ProCSharp.Collections
{
class Program
{
static void Main(string[] args)
{
var books = new SortedList<string, string>();
books.Add("C# 2008 Wrox Box", "978–0–470–047205–7");
books.Add("Professional ASP.NET MVC 1.0", "978–0–470–38461–9");
books["Beginning Visual C# 2008"] = "978–0–470-19135-4";
books["Professional C# 2008"] = "978–0–470–19137–6";
foreach (KeyValuePair<string, string> book in books)
{
Console.WriteLine("{0}, {1}", book.Key, book.Value);
}
foreach (string isbn in books.Values)
{
Console.WriteLine(isbn);
}
foreach (string title in books.Keys)
{
Console.WriteLine(title);
}
{
string isbn;
string title = "Professional C# 7.0";
// 出現異常
try
{
isbn = books[title];
}
catch ( KeyNotFoundException err)
{
Console.WriteLine("Exception " + err.ToString());
}
if (!books.TryGetValue(title, out isbn))
{
Console.WriteLine("{0} not found", title);
}
}
}
}
}
5.LinkList<T>
LinkList<T>才是連結串列而且是雙向連結串列,前面的都是基於陣列的。連結串列典型的特徵就是插入刪除非常方便,但是查詢比較慢需要O(n)的查詢效率。
LinkedList<T>包含LinkedListNode<T>型別的元素,該節點定義了List、Next、Previous、Value,List屬性返回和節點相關的LinkedList<T>物件。LinkedList<T>可以訪問成員的第一個和最後一個元素(First和Last);可以在指定位置AddAfter()/AddBefore()/AddFirst()/AddLast()方法;刪除指定位置的元素Remove()/RemoveFirst()/RemoveLast()方法;查詢Find()和FindLast()。
LinkList<T>例項:
Document.cs:
namespace Wrox.ProCSharp.Collections
{
public class Document
{
public string Title { get; private set; }
public string Content { get; private set; }
public byte Priority { get; private set; }
public Document(string title, string content, byte priority = 0)
{
this.Title = title;
this.Content = content;
this.Priority = priority;
}
}
}
PriorityDocumentManager.cs:
using System;
using System.Collections.Generic;
namespace Wrox.ProCSharp.Collections
{
public class PriorityDocumentManager
{
// 真正存放排序好的document的結構體
private readonly LinkedList<Document> documentList;
// priorities 0.9, 方便索引documentList本優先順序的最後一個元素;
// 用一個List<T>陣列索引器,提高效率,找到陣列和連結串列之間平衡點的提高效能的好方法。
private readonly List<LinkedListNode<Document>> priorityNodes;
public PriorityDocumentManager()
{
documentList = new LinkedList<Document>();
priorityNodes = new List<LinkedListNode<Document>>(10);
for (int i = 0; i < 10; i++)
{
priorityNodes.Add(new LinkedListNode<Document>(null));
}
}
// 對外介面
public void AddDocument(Document d)
{
if (d == null) throw new ArgumentNullException("d");
AddDocumentToPriorityNode(d, d.Priority);
}
private void AddDocumentToPriorityNode(Document doc, int priority)
{
// 外部呼叫要保證priority就是doc.priority, 否則後面會導致問題
if (priority > 9 || priority < 0)
throw new ArgumentException("Priority must be between 0 and 9");
// 1.開始空或者中間空,遞迴會導致這裡不進來!=null(因為小優先順序的有元素時候)
if (priorityNodes[priority].Value == null)
{
--priority;
if (priority >= 0)
{
// check for the next lower priority
// 2)遞迴是為了檢測小於優先順序的有沒有存在元素的,這時priority會小於doc.priority
AddDocumentToPriorityNode(doc, priority);
}
else // now no priority node exists with the same priority or lower
// add the new document to the end
{
// 1)第一次會進來或者當前priority以下的優先順序都沒有的情況也會進來
// 更小優先順序的都沒有,那麼它就是最小優先順序的
documentList.AddLast(doc);
// priorityNodes存放的時連結串列最後的那個元素,doc.Priority和documentList.Last上的優先順序一樣的
priorityNodes[doc.Priority] = documentList.Last;
}
return;
}
// 直接進來,或者遞迴進來,說明當前優先順序或者遞迴減到的優先順序有元素。
else // a priority node exists
{
// 從priorityNodes獲取的是當前優先順序,最後一個節點的元素
LinkedListNode<Document> prioNode = priorityNodes[priority];
// 1)直接進來時候,如果優先順序相等,如果是遞迴進來的不會到這裡因為priority變小了
if (priority == doc.Priority)
// priority node with the same priority exists
{
// 是當前優先順序直接新增到末尾
documentList.AddAfter(prioNode, doc);
// set the priority node to the last document with the same priority
// 當前優先順序存的是優先順序最後的節點,doc.Priority和prioNode.Next;上的優先順序一樣的
priorityNodes[doc.Priority] = prioNode.Next;
}
// 2)遞迴時候進來的,因為priority 小於了doc.Priority,且priority有值,所以要放到priority前面
else // only priority node with a lower priority exists
{
// get the first node of the lower priority
LinkedListNode<Document> firstPrioNode = prioNode;
while (firstPrioNode.Previous != null &&
firstPrioNode.Previous.Value.Priority == prioNode.Value.Priority)
{
firstPrioNode = prioNode.Previous;
prioNode = firstPrioNode;
}
//沒有放到前面,為了連結串列按照優先順序大在前面
documentList.AddBefore(firstPrioNode, doc);
// set the priority node to the new value
// 當前優先順序存的是優先順序最後的節點,doc.Priority和prioNode.Next;上的優先順序一樣的
priorityNodes[doc.Priority] = firstPrioNode.Previous;
}
}
}
// 按照從大優先順序,相同優先順序先來優先順序高的順序排序
public void DisplayAllNodes()
{
foreach (Document doc in documentList)
{
Console.WriteLine("priority: {0}, title {1}", doc.Priority, doc.Title);
}
}
// returns the document with the highest priority
// (that's first in the linked list)
// 優先順序高的出連結串列,並且刪除該document
public Document GetDocument()
{
Document doc = documentList.First.Value;
documentList.RemoveFirst();
return doc;
}
}
}
Program.cs:
namespace Wrox.ProCSharp.Collections
{
class Program
{
static void Main()
{
PriorityDocumentManager pdm = new PriorityDocumentManager();
// 傳入時候就排序好了,LinkList<T>結構方便優先順序型別的插入操作(利於插入和刪除)
pdm.AddDocument(new Document("one", "Sample", 8));
pdm.AddDocument(new Document("two", "Sample", 3));
pdm.AddDocument(new Document("three", "Sample", 4));
pdm.AddDocument(new Document("four", "Sample", 8));
pdm.AddDocument(new Document("five", "Sample", 1));
pdm.AddDocument(new Document("six", "Sample", 9));
pdm.AddDocument(new Document("seven", "Sample", 1));
pdm.AddDocument(new Document("eight", "Sample", 1));
// 展示排序好的
pdm.DisplayAllNodes();
}
}
}
四.Dictionary<TKey,TValue>、多鍵值ILookup<TKey,TValue>、SortedDictionary<TKey,TValue>、HashSet<T>和SortedSet<T>
1.Dictionary<TKey,TValue>
.net提供了幾個字典類,其中最主要的類是Dictionary<TKey,TValue>。
字典基於hash_map儲存結構,提供了快速的查詢方法,查詢效率是O(1),但是也不是絕對的因為要解決hash對映函式計算和解決衝突。
也可以自由的新增和刪除元素,有點像List<T>但是沒有記憶體元素挪動效能開銷。
Dictionary資料結構很類似C++中的hash_map/unordered_map工作方式,或者就是這樣的實現:
hash_map其插入過程是:
得到key
通過hash函式得到hash值
得到桶號(一般都為hash值對桶數求模)
存放key和value在桶內。
其取值過程是:
得到key
通過hash函式得到hash值
得到桶號(一般都為hash值對桶數求模)
比較桶的內部元素是否與key相等,若都不相等,則沒有找到。
取出相等的記錄的value。
因此C#中要用Dictionary類,鍵型別需要重寫:
1)雜湊函式:Object類的GetHashCode()方法,GetHashCode()返回int值用於計算鍵對應位置放置的hashCode用作元素索引。
GetHashCode()實現要求:
相同的鍵總是返回相同的int值,不同的鍵可以返回相同的int值。
它應該執行得比較快,計算開銷不大,hashCode應該儘量平均分佈在int可以儲存的整個數字範圍上。
不能丟擲異常。
至少使用一個鍵物件的欄位,hashCode最好在鍵物件的生存期中不發生變化。
2)解決衝突:鍵型別必須實現IEquatable<T>.Equals()方法,或者重寫Object類的Equals()方法,因為不同的鍵值需要返回不同的hashCode,相同的鍵返回相同hashCode。
預設沒有重寫,那麼Equals方法比較的是引用無論是值型別還是引用型別,GetHashCode()是根據物件的地址計算hashCode,所以預設是基於引用的比較。
相同的int型別傳入,只要不是相同的int引用,就會導致無法返回結果。
所以基礎型別都重寫了上述兩個方法,基礎型別中string比較通過字串值有較好的雜湊平均分佈,int也是通過值比較但是很難平均分佈。
如果重寫了一個Equals方法(一般是值比較),但是沒有重寫GetHashCode()方法(一般也是基於值的獲取hashCode)那麼獲取hashCode的方法將是獲取引用,使用字典類就會導致詭異的行為,將物件放入了字典中,但是取不出來了(因為鍵引用不同),或者取出來的是一個錯誤的結果,所以編譯器會給一個編譯警告!
如果鍵型別沒有重寫GetHashCode()和Equals()方法;也可以實現IEqualityComparer<T>介面的比較器它定義了GetHashCode()和Equals()方法,並將傳遞的物件作為引數,將比較器傳入Dictionary<TKey,TValue>一個過載版本的建構函式即可。
Employee.cs:
using System;
namespace Wrox.ProCSharp.Collections
{
// 將類的一個例項序列化為一個檔案
[Serializable]
public class Employee
{
private string name;
private decimal salary;
private readonly EmployeeId id;
public Employee(EmployeeId id, string name, decimal salary)
{
this.id = id;
this.name = name;
this.salary = salary;
}
public override string ToString()
{
return String.Format("{0}: {1, -20} {2:C}",
id.ToString(), name, salary);
}
}
}
EmployeeId.cs:
using System;
namespace Wrox.ProCSharp.Collections
{
[Serializable]
public class EmployeeIdException : Exception
{
public EmployeeIdException(string message) : base(message) { }
}
[Serializable]
public struct EmployeeId : IEquatable<EmployeeId>
{
private readonly char prefix;
private readonly int number;
public EmployeeId(string id)
{
if (id == null) throw new ArgumentNullException("id");
prefix = (id.ToUpper())[0];
int numLength = id.Length - 1;
try
{
// 擷取前面6位,有可能提供的number一樣,這樣多個鍵會產生一個相同的GetHashCode()。
// 但是後面會通過Equals方法進行解決衝突。
number = int.Parse(id.Substring(1, numLength > 6 ? 6 : numLength));
}
catch (FormatException)
{
throw new EmployeeIdException("Invalid EmployeeId format");
}
}
public override string ToString()
{
return prefix.ToString() + string.Format("{0,6:000000}", number);
}
// 獲取確定的,int型別上均勻分配的,高效能的產生hashCode方法
public override int GetHashCode()
{
return (number ^ number << 16) * 0x15051505;
}
public bool Equals(EmployeeId other)
{
if (other == null) return false;
// number相同情況下,如果prefix也相同,那麼就會導致完全相同了
return (prefix == other.prefix && number == other.number);
}
public override bool Equals(object obj)
{
return Equals((EmployeeId)obj);
}
public static bool operator ==(EmployeeId left, EmployeeId right)
{
return left.Equals(right);
}
public static bool operator !=(EmployeeId left, EmployeeId right)
{
return !(left == right);
}
}
}
Program.cs:
using System;
using System.Collections.Generic;
namespace Wrox.ProCSharp.Collections
{
class Program
{
static void Main()
{
// capacity是素數
var employees = new Dictionary<EmployeeId, Employee>(31);
var idKyle = new EmployeeId("T3755");
var kyle = new Employee(idKyle, "Kyle Bush", 5443890.00m);
employees.Add(idKyle, kyle);
Console.WriteLine(kyle);
var idCarl = new EmployeeId("F3547");
var carl = new Employee(idCarl, "Carl Edwards", 5597120.00m);
employees.Add(idCarl, carl);
Console.WriteLine(carl);
var idJimmie = new EmployeeId("C3386");
var jimmie = new Employee(idJimmie, "Jimmie Johnson", 5024710.00m);
var jimmie2 = new Employee(idJimmie, "Jimmie Cen", 5024710.00m);
employees.Add(idJimmie, jimmie);
//employees.Add(idJimmie, jimmie2); // 相同key,用Add不會覆蓋,但是會丟擲異常
Console.WriteLine(jimmie);
var idDale = new EmployeeId("C3323");
var dale = new Employee(idDale, "Dale Earnhardt Jr.", 3522740.00m);
employees[idDale] = dale;
Console.WriteLine(dale);
var idJeff = new EmployeeId("C3234");
var jeff = new Employee(idJeff, "Jeff Burton", 3879540.00m);
var jeff2 = new Employee(idJeff, "Jeff Cen", 3879540.00m);
// 下標索引方式新增元素
employees[idJeff] = jeff;
employees[idJeff] = jeff2; // 相同key,用下標索引會覆蓋
Console.WriteLine(jeff);
while (true)
{
Console.Write("Enter employee id (X to exit)> ");
var userInput = Console.ReadLine();
userInput = userInput.ToUpper();
if (userInput == "X") break;
EmployeeId id;
try
{
// 第一位字元會去掉,用後面的數字作為真正的key
id = new EmployeeId(userInput);
Employee employee;
// 如果用下標訪問的話,不存在會丟擲異常NotFoundException
if (!employees.TryGetValue(id, out employee))
{
Console.WriteLine("Employee with id {0} does not exist", id);
}
else
{
Console.WriteLine(employee);
}
}
catch (EmployeeIdException ex)
{
Console.WriteLine(ex.Message);
}
}
}
}
}
2.ILookup<TKey,TValue>
非常類似於Dictionary<TKey,TValue>,會把鍵對映到一個值集上,但是ILookup<TKey,TValue>是在System.Core名稱空間中,用System.Linq名稱空間定義。
ILookup<TKey,TValue>是一個拓展結構,不能像其它容器那樣直接建立,需要從實現了IEnumerable<T>介面的容器中用ToLookup函式獲取。
ToLookup函式需要傳遞一個Func<TSource, TKey>型別的鍵委託,可以用Lambda表示式來實現,例如:
static void Main()
{
var racers = new List<Racer>();
racers.Add(new Racer(26, "Jacques", "Villeneuve", "Canada", 11));
racers.Add(new Racer(18, "Alan", "Jones", "Australia", 12));
racers.Add(new Racer(11, "Jackie", "Stewart", "United Kingdom", 27));
racers.Add(new Racer(15, "James", "Hunt", "United Kingdom", 10));
racers.Add(new Racer(5, "Jack", "Brabham", "Australia", 14));
//public static ILookup<TKey, TSource> ToLookup<TSource, TKey>
//(this IEnumerable<TSource> source, Func<TSource, TKey> keySelector);
//建立一個1:n 的對映。 它可以方便的將資料分類成組,並生成一個字典供查詢使用。
//System.Linq.Enumerable::ToLookup
//public static ILookup<TKey, TSource> ToLookup<TSource, TKey>(this IEnumerable<TSource> source, Func<TSource, TKey> keySelector);
// this IEnumerable<TSource> source?,是.net的拓展方法機制,用例項呼叫靜態方法,但是編譯器是將例項作為靜態方法的第一個引數來呼叫靜態方法的。
var lookupRacers = racers.ToLookup(x => x.Country);
foreach (Racer r in lookupRacers["Australia"])
{
Console.WriteLine(r);
}
}
3.SortedDictionary<TKey,TValue>
是一個二叉樹,其中元素根據鍵來排序,該鍵型別必須實現IComparable<TKey>介面,或者需要傳遞一個IComparer<TKey>介面的比較器用作有序字典的建構函式的一個引數。其實類似於C++中
的map型別了,類似java中的TreeMap型別。
SortedDictionary<TKey,TValue>和SortedList<TKey,TValue>,但SortedDictionary<TKey,TValue>類插入和刪除元素比較快,查詢速度比較慢,記憶體開銷比SortedList<TKey,TValue>大。
SortedList<TKey,TValue>適用很少修改的情形,因為有更快的查詢速度,用更小的記憶體。
4.HashSet<T>和SortedSet<T>
HashSet<T>是無序的和SortedSet<T>是有序的都實現了介面ISet<T>,ISet<T>提供了集合的交集,並集,判斷集合關係等操作。
直接建立集合物件就可以了,為集合新增元素可以用Add()方法如果重複那麼會返回false不會丟擲異常。
IsSubsetOf和IsSupersetOf方法比較集合實現了IEnumerable<T>介面的集合,並返回一個布林結果,Overlaps()是判斷有交集。
UnionWith()方法將多個集合求並,ExceptWith()求差集。
SortedSet<T>如果是自定義型別,那麼需要提供排序的委託例項。
ObservableCollection<T>類,是為WPF定義的,這樣UI可以得到集合的變化,在名稱空間:System.Collections.ObjectModel中定義。
ObservableCollection<T>派生自Collection<T>基類,所以集合類的很多操作該容器都滿足,並在內部使用了List<T>類。
ObservableCollection<T>物件的CollectionChanged事件可以新增訊息處理函式也就是委託例項,當集合發生變化時候可以回撥到處理函式中。
五.BitArray/BitVector32、IProducerConsumerCollection<T>介面
1.BitArray/BitVector32用集合進行位操作
1).BitArray位於System.Collections名稱空間中,用於不確定位大小的操作,可以包含非常多的位,應該是儲存在堆中。
BitArray是一個引用型別,包含一個int陣列,其中每32位使用一個新整數。
BitArray可以用索引器對陣列中的位進行操作,索引器是bool型別,還可以用Get(),Set方法訪問陣列中的位。
BitArray的位操作,Not非,And()與,Or()或,Xor()異或操作。
2).BitVector32是值型別
BitVector32位於System.Collections.Specialized中,32位的操縱,儲存在棧中,速度很快。
BitVector32屬性方法,Data返回二進位制資料的整型大小.
BitVector32的訪問可以使用索引器,索引器是過載的,可以使用掩碼或BitVector32.Section型別的片段來獲取和設定值。
CreateMask()為結構中的特定位建立掩碼。
CreateSection()用於建立32位中的幾個片段。
位操作例子:
using System;
using System.Collections;
using System.Collections.Specialized;
using System.Text;
namespace BitArraySample
{
class Program
{
static void Main()
{
BitArrayDemo();
BitVectorDemo();
}
static void BitArrayDemo()
{
var bits1 = new BitArray(8);
// 全部設定為1
bits1.SetAll(true);
// 索引1設定為false
bits1.Set(1, false);
// 設定用下標索引器設定值
bits1[5] = false;
bits1[7] = false;
Console.Write("initialized: ");
DisplayBits(bits1);
Console.WriteLine();
// 位的一些運算
DisplayBits(bits1);
bits1.Not();
Console.Write(" not ");
DisplayBits(bits1);
Console.WriteLine();
var bits2 = new BitArray(bits1);
bits2[0] = true;
bits2[1] = false;
bits2[4] = true;
DisplayBits(bits1);
Console.Write(" or ");
DisplayBits(bits2);
Console.Write(" : ");
bits1.Or(bits2);
DisplayBits(bits1);
Console.WriteLine();
DisplayBits(bits2);
Console.Write(" and ");
DisplayBits(bits1);
Console.Write(" : ");
bits2.And(bits1);
DisplayBits(bits2);
Console.WriteLine();
DisplayBits(bits1);
Console.Write(" xor ");
DisplayBits(bits2);
bits1.Xor(bits2);
Console.Write(" : ");
DisplayBits(bits1);
Console.WriteLine();
}
static void BitVectorDemo()
{
var bits1 = new BitVector32();
// 書面寫法,不考慮大小端,最後一個位的掩碼(考慮是小端也就是地址讀到的第一個)
int bit1 = BitVector32.CreateMask();
// 基於bit1位的上一個位的掩碼,其實bit1是1
int bit2 = BitVector32.CreateMask(bit1);
// 基於bit2位的上一個位的掩碼,其實bit2是2
int bit3 = BitVector32.CreateMask(bit2);
int bit4 = BitVector32.CreateMask(bit3);
int bit5 = BitVector32.CreateMask(bit4);
// 用索引器將末尾取值為1,其實bit1是1
bits1[bit1] = true;
// 用索引器將倒數第二位取值為0,其實bit2是2
bits1[bit2] = false;
bits1[bit3] = true;
bits1[bit4] = true;
Console.WriteLine(bits1);
// 可以一次性的把有1下標的賦值為true
bits1[0xabcdef] = true;
Console.WriteLine(bits1);
int received = 0x79abcdef;
// 一次性的把有1下標的賦值為true,為0的賦值位0
var bits2 = new BitVector32(received);
Console.WriteLine(bits2);
// sections: FF EEE DDD CCCC BBBBBBBB AAAAAAAAAAAA
// 從底地址開始擷取0xfff片段的索引值
BitVector32.Section sectionA = BitVector32.CreateSection(0xfff);
// 基於sectionA偏移,取0xff位數上的索引值
BitVector32.Section sectionB = BitVector32.CreateSection(0xff, sectionA);
BitVector32.Section sectionC = BitVector32.CreateSection(0xf, sectionB);
BitVector32.Section sectionD = BitVector32.CreateSection(0x7, sectionC);
BitVector32.Section sectionE = BitVector32.CreateSection(0x7, sectionD);
BitVector32.Section sectionF = BitVector32.CreateSection(0x3, sectionE);
// 用索引片段,訪問bits2在該片段上的元素
Console.WriteLine("Section A: " + IntToBinaryString(bits2[sectionA], true));
Console.WriteLine("Section B: " + IntToBinaryString(bits2[sectionB], true));
Console.WriteLine("Section C: " + IntToBinaryString(bits2[sectionC], true));
Console.WriteLine("Section D: " + IntToBinaryString(bits2[sectionD], true));
Console.WriteLine("Section E: " + IntToBinaryString(bits2[sectionE], true));
Console.WriteLine("Section F: " + IntToBinaryString(bits2[sectionF], true));
}
static string IntToBinaryString(int bits, bool removeTrailingZero)
{
var sb = new StringBuilder(32);
for (int i = 0; i < 32; i++)
{
// 從左邊讀起,讀完後丟棄左邊位數,所以與上0x80000000
if ((bits & 0x80000000) != 0)
{
sb.Append("1");
}
else
{
sb.Append("0");
}
bits = bits << 1;
}
string s = sb.ToString();
if (removeTrailingZero)
return s.TrimStart('0');
else
return s;
}
static void DisplayBits(BitArray bits)
{
// 可以直接迭代輸出
foreach (bool bit in bits)
{
Console.Write(bit ? 1 : 0);
}
}
}
}
2.IProducerConsumerCollection<T>介面
.net4.0中包含了新的名稱空間System.Collections.Concurrent,定義了一些執行緒安全的集合,這些集合實現了IProducerConsumerCollection<T>介面(生產消費者模式)。這樣多執行緒訪問這些集合就不需要Lock{}操作了。只需要用相應集合的TryAdd和TryTake方法,這兩個方法分為阻塞的和非阻塞的,如果失敗會返回false否則返回true。
這些集合有:
ConcurrentQueue<T>集合,Enqueue(),TryDequeue()和TryPeek方法。
ConcurrentStack<T>類。
ConcurrentBag<T>類。
ConcurrentDictionary<TKey, TValue>這個是執行緒安全的鍵值集合,TryAdd,TryGetValue, TryRemove,TryUpdate方法以非阻塞方式訪問成員。因為基於鍵和值ConcurrentDictionary<TKey,
TValue>沒有實現IProducerConsumerCollection<T>介面,但是也是支援執行緒安全的。
ConcurrentXXX型別的集合是執行緒安全的。
BlockingCollection<T>定義了集合操作阻塞的介面,使用令牌機制,可以指定等待的最長時間。BlockingCollection<T>是針對實現了IProducerConsumerCollection<T>介面的任意型別修飾器,預設是使用了ConcurrentQueue<T>類。
實現例子:
static void Main()
{
BlockingDemoSimple();
}
static void BlockingDemoSimple()
{
// 阻塞的容器
var sharedCollection = new BlockingCollection<int>();
// 定義兩個事件物件和等待事件完成的重置訊息控制代碼
var events = new ManualResetEventSlim[2];
var waits = new WaitHandle[2];
for (int i = 0; i < 2; i++)
{
events[i] = new ManualResetEventSlim(false);
waits[i] = events[i].WaitHandle;
}
var producer = new Thread(obj =>
{
// 解析傳入的集合容器和事件物件
var state = (Tuple<BlockingCollection<int>, ManualResetEventSlim>)obj;
var coll = state.Item1;
var ev = state.Item2;
var r = new Random();
for (int i = 0; i < 300; i++)
{
// 阻塞函式,var coll前面強轉確定型別,可以新增元素
coll.Add(r.Next(3000));
}
// 事件完成,釋放訊號
ev.Set();
});
producer.Start(Tuple.Create<BlockingCollection<int>, ManualResetEventSlim>(sharedCollection, events[0]));
var consumer = new Thread(obj =>
{
// 解析傳入的集合容器和事件物件
var state = (Tuple<BlockingCollection<int>, ManualResetEventSlim>)obj;
var coll = state.Item1;
var ev = state.Item2;
for (int i = 0; i < 300; i++)
{
// 阻塞函式,前面強轉確定型別,提取元素
int result = coll.Take();
}
// 事件完成,釋放訊號
ev.Set();
});
consumer.Start(Tuple.Create<BlockingCollection<int>, ManualResetEventSlim>(sharedCollection, events[1]));
// 主執行緒會阻塞一直等待訊號到來
if (!WaitHandle.WaitAll(waits))
Console.WriteLine("wait failed");
else
Console.WriteLine("reading/writing finished");
}