字典(dictionary)是一個集合,其中每一個元素都是一個鍵/值對。字典(Dictionaries)是經常使用於查找和排序的列表。算法
.NET Framework經過IDictionary接口和IDictionary<TKey,TValue>接口,以及一些經常使用的子典了定義了子典協議。每一個類在如下方面各有不一樣:數組
下表總結了每一個字典類,以及它們在上述這幾個方面的差別。它們都是在一個1.5G的PC上執行5000次操做獲得的一個平均值。數據結構
Type | 內部結構 | 支持索引 | 內存佔用 | 隨機插入的速度(毫秒) | 順序插入的速度(毫秒) | 根據鍵獲取元素的速度(毫秒) |
未排序字典 | ||||||
Dictionary<T,V> | 哈希表 | 否 | 22 | 30 | 30 | 20 |
Hashtable | 哈希表 | 否 | 38 | 50 | 50 | 30 |
ListDictionary | 鏈表 | 否 | 36 | 50000 | 50000 | 50000 |
OrderedDictionary | 哈希表 +數組 |
是 | 59 | 70 | 70 | 40 |
排序字典 | ||||||
SortedDictionary<K,V> | 紅黑樹 | 否 | 20 | 130 | 100 | 120 |
SortedList<K,V> | 2xArray | 是 | 20 | 3300 | 30 | 40 |
SortList | 2xArray | 是 | 27 | 4500 | 100 | 180 |
從時間複雜度來說,從字典中經過鍵獲取值所耗費的時間分別以下:app
n是集合元素的數量。ide
IDictionary<TKey,Tvalue>指定了全部以key/value爲基礎集合的標準協議。因爲它添加了方法和屬性用以經過鍵讀取元素,從而擴展了ICollection<T>接口:性能
public interface IDictionary <TKey, TValue> : ICollection <KeyValuePair <TKey, TValue>>, IEnumerable { bool ContainsKey (TKey key); bool TryGetValue (TKey key, out TValue value); void Add (TKey key, TValue value); bool Remove (TKey key); TValue this [TKey key] { get; set; } // Main indexer - by key ICollection <TKey> Keys { get; } // Returns just keys ICollection <TValue> Values { get; } // Returns just values }
向字典中添加一個元素,你能夠調用add方法,或者經過索引器的set方法;對於後者,若是添加元素的鍵在字段中不存在,那麼把該元素插入到字典中;不然更新字典中相同鍵對應的值。全部的字典實現類都不接受重複鍵,因此兩次調用add方法時使用相同鍵則會拋出異常。測試
從字段中獲取一個元素,可使用索引器的get方法或者調用TryGetValue方法。若是鍵不存在,使用索引器方法會拋出異常,而TryGetValue返回false。你可經過ContainsKey方法來確認某一個鍵是否在字典中存在;可是這樣會致使額外的查詢開銷。ui
能夠經過KeyValuePari結構來遍歷IDictionary<TKey,TValue>。this
[Serializable] public struct KeyValuePair<TKey, TValue> { private TKey key; private TValue value; public KeyValuePair(TKey key, TValue value) { this.key = key; this.value = value; } public TKey Key { get { return key; } } public TValue Value { get { return value; } } public override string ToString() { StringBuilder s = StringBuilderCache.Acquire(); s.Append('['); if( Key != null) { s.Append(Key.ToString()); } s.Append(", "); if( Value != null) { s.Append(Value.ToString()); } s.Append(']'); return StringBuilderCache.GetStringAndRelease(s); } }
固然,你也能夠經過字典的Keys或Values屬性遍歷字典的全部鍵或值。在Dictionary類中,將演示該接口是如何使用的。spa
IDictionary是非generic的字典接口;與IDictionary<TKey, TValue>比較有兩處不一樣:
public interface IDictionary : ICollection { // Interfaces are not serializable // The Item property provides methods to read and edit entries // in the Dictionary. Object this[Object key] { get; set; } // Returns a collections of the keys in this dictionary. ICollection Keys { get; } // Returns a collections of the values in this dictionary. ICollection Values { get; } // Returns whether this dictionary contains a particular key. // bool Contains(Object key); // Adds a key-value pair to the dictionary. // void Add(Object key, Object value); // Removes all pairs from the dictionary. void Clear(); bool IsReadOnly { get; } bool IsFixedSize { get; } // Returns an IDictionaryEnumerator for this dictionary. new IDictionaryEnumerator GetEnumerator(); // Removes a particular key from the dictionary. // void Remove(Object key); }
經過DictionaryEntry接口來遍歷非generic的字典
[Serializable] public struct DictionaryEntry { private Object _key; private Object _value; // Constructs a new DictionaryEnumerator by setting the Key // and Value fields appropriately. public DictionaryEntry(Object key, Object value) { _key = key; _value = value; } public Object Key { get { return _key; } set { _key = value; } } public Object Value { get { return _value; } set { _value = value; } } }
geneirc的Dictionary類是使用最多的集合類(此外,就是List<T>集合類)。Dictionary<TKey, TValue>使用哈希數據結構來存儲鍵和值,所以它既快速又高效。
非generic的Dictionary<TKey, TValue>就是Hashtable;所以不存在非generic的類Dictionary。當咱們說起Dictionary時,咱們通常是指Dictionary<TKey, TValue>。
Dictionary實現了generic和非generic的IDictionary接口,generic的IDictonary都暴露爲public。實際上,Dictionary若是教科書通常地實現了generic的IDictionary接口。
下面的代碼演示瞭如何使用Ditionary<TKey, TValue>類:
var d = new Dictionary<string, int>(); d.Add("One", 1); d["Two"] = 2; // adds to dictionary because "two" is not already present d["Two"] = 22; // updates dictionary because "two" is now present d["Three"] = 3; Console.WriteLine (d["Two"]); // Prints "22" Console.WriteLine (d.ContainsKey ("One")); // true (fast operation) Console.WriteLine (d.ContainsValue (3)); // true (slow operation) int val = 0; if (!d.TryGetValue ("onE", out val)) Console.WriteLine ("No val"); // "No val" (case sensitive) // Three different ways to enumerate the dictionary: foreach (KeyValuePair<string, int> kv in d) // One ; 1 Console.WriteLine (kv.Key + "; " + kv.Value); // Two ; 22 // Three ; 3 foreach (string s in d.Keys) Console.Write (s); // OneTwoThree Console.WriteLine(); foreach (int i in d.Values) Console.Write (i); // 1223
該類背後的哈希表,把每一個鍵都轉換成一個整數型的哈希碼,而後經過算法將其轉換成一個哈希鍵。在內部經過哈希鍵肯定一個成員屬於哪個「桶」;若是一個「桶」包含多個值,那麼對該「桶」執行線型搜索。一個好的哈希算法,不只努力實現返回一個嚴格的哈希碼,並且還努力實現所返回的哈希碼在32位的整數中均勻地分佈。
字典能夠包含任何類型的鍵,只要這些鍵支持是否相等接口並能獲取哈希碼。在默認狀況下,鍵的相等性取決於對象的Equals方法,而計算哈希鍵的算法也基於對象的GetHashCode方法。這些行爲不是一成不變的,若是重載了Equals方法或GetHashCode方法,或在建立字典實例時提供了IEqualityComparer實例對象。一個常見的應用就是在使用字符串字段時,提供了區分大小寫的相等性比較器實例。
var d = new Dictionary<string, int> (StringComparer.OrdinalIgnoreCase);
與其它集合類型同樣,若是在構造字典實例時,指定字段的大小,那麼能夠在必定程度上改善性能。指定字典的大小,能夠避免或減小內部調正大小的操做。
Dictioanry和Hashtable的缺點是items並無排序。甚至,添加到字典中的成員也不會保留原有的順序。此外,字典還有一個缺點就是不接收重複的鍵。
OrderedDictionary是非generic的字典類,它保存了成員原有的順序。使用OrderedDictioanry時,你能夠經過索引或鍵獲取字段元素。
OrderedDictionary結合了Hashtable和ArrayList。這就意味着,它不只有Hashtable的全部功能,還有RemoveAt,整數索引器方法。它還根據元素的原始順序對外暴露Keys和Values屬性。
該類在.NET 2.0中引入,並且沒有對應的非generic版本。
ListDictionary使用單鏈表存儲數據。它不提供排序,儘管它保留了元素的原始順序。當集合很大時,其性能至關低。它值得注意的地方僅僅在於當元素數量很小時有效率(元素少於10個)。
HybirdDictionary是一個ListDictionary,它會當元素數量達到必定數量後自動轉換成Hashtable,以解決ListDictionary的性能問題。這種想法可使得字典元素不多時,佔用較低的內存;而字典數量較大時擁有較好的性能。然而,在到了必定的數目後須要從一個數據類型轉換成另外一個數據類型--而Dictionary在這兩種狀況下都不會太慢或性能低--所以,你爲什麼不在一開始就使用Dicontary類。
此外,這兩個類都是非generic的類。
Framework提供了兩個字典類,它們經過排序的鍵來構建。這兩個類就是SortedDictoanry<TKey, TValue>和 SortedList<Tkey,TValue>。
SortedDictoanry<TKey, TValue>,使用紅黑樹:一種數據結構,該數據結構保證了任何插入和獲取元素行爲都是一致地。
SortedList<Tkey,TValue>,內部由一個排序後的數組對實現,能夠實現快速讀取,可是插入性能較差。
SortedDictoanry<TKey, TValue>比SortedList快,按照隨機順序插入元素。 SortedList,有一個額外的功能,能夠經過索引或鍵獲取元素。 使用排序後的列表,你能夠直接找到第幾個元素。 而若是想在SortedDictionary中實現一樣的目的,那麼你須要手動的遍歷n個元素。
下面的例子演示了使用反射加載全部System.Object類的方法到一個排序後的列表,而後遍歷該列表的鍵和值
var sorted = new SortedList <string, MethodInfo>(); foreach (MethodInfo m in typeof (object).GetMethods()) sorted [m.Name] = m; foreach (string name in sorted.Keys) Console.WriteLine (name); foreach (MethodInfo m in sorted.Values) Console.WriteLine (m.Name + " returns a " + m.ReturnType);
第一個列表的結果以下:
Equals |
第二個的列表的結果以下:
Equals returns a System.Boolean |
請注意,咱們經過索引器填充字段類。若是咱們使用add方法,那麼會拋出異常,這是由於咱們所依賴的對象類重載了Equals方法,而你不能添加劇復的鍵到一個字典中。而使用索引器,這會避免該問題。
若是擴展咱們的示例,下面的代碼則會返回GetHashCode方法,其使用方法和一個普通的字典的使用方式同樣
Console.WriteLine (sorted ["GetHashCode"]);
Console.WriteLine (sorted.Keys [sorted.Count - 1]); // ToString Console.WriteLine (sorted.Values[sorted.Count - 1].IsVirtual); // True