.net源碼分析 – Dictionary

接上篇:.net源碼分析 – List<T>html

Dictionary<TKey, TValue>源碼地址:https://github.com/dotnet/corefx/blob/master/src/System.Collections/src/System/Collections/Generic/Dictionary.csgit

接口

Dictionary<TKey, TValue>和List<T>的接口形式差很少,不重複說了,能夠參考List<T>那篇。 github

變量

看下有哪些成員變量:數組

 1 private int[] buckets;
 2 private Entry[] entries;
 3 private int count;
 4 private int version;
 5 
 6 private int freeList;
 7 private int freeCount;
 8 private IEqualityComparer<TKey> comparer;
 9 private KeyCollection keys;
10 private ValueCollection values;
11 private Object _syncRoot;

buckets是一個int型數組,具體什麼用如今還未知,後面看,暫時能夠理解成區,像硬盤咱們通常會作分區歸類方便查找。安全

entries是Entry數組,看看Entry:多線程

1 private struct Entry
2 {
3     public int hashCode;    // Lower 31 bits of hash code, -1 if unused
4     public int next;        // Index of next entry, -1 if last
5     public TKey key;           // Key of entry
6     public TValue value;         // Value of entry
7 }

是個結構,裏面有key, value, 說明咱們Dictionary的key和value就是用這個結構保存的,另外還有hashcode和next,看起來像鏈表同樣,後面用到時再具體分析其用處。ide

count:和List <T>同樣,是指包括元素的個數(這裏其實也不是真正的個數,下面會講),並非容量函數

version: List <T>篇講過,用來遍歷時禁止修改集合源碼分析

freeList, freeCount這兩個看起來比較奇怪,比較難想到會有什麼用,在添加和刪除項時會用到它們,後面再講。性能

comparer: key的比較對象,能夠用它來獲取hashcode以及進行比較key是否相同

keys, values這個咱們日常也有用到,遍歷keys或values有用

_syncRoot,List<T>篇也講過,線程安全方面的,Dictionary一樣沒有用到這個對象,Dictionary也不是線程安全的,在多線程環境下使用須要本身加鎖。

例子

Dictionary的代碼比List相對複雜些,下面不直接分析源碼,而是如下面這些經常使用例子來一步一步展現Dictionary是怎麼工做的:

 1 Dictionary<string, string> dict = new Dictionary<string, string>();
 2 
 3 dict.Add("a", "A");
 4 
 5 dict.Add("b", "B");
 6 
 7 dict.Add("c", "C");
 8 
 9 dict["d"] = "D";
10 
11 dict["a"] = "AA";
12 
13 dict.remove("b");
14 
15 dict.Add("e", "E");
16 
17 var a = dict["a"];
18 
19 var hasA = dict.ContainsKey("a");

這裏對hashcode作些假設,方便分析:

"a"的hashcode爲3

"b"的hashcode爲4

"c"的hashcode爲6

"d"的hashcode爲11

"e"的hashcode爲10

構造函數

先看第一句,new 一個Dictionary<string, string>,看源碼裏的構造函數,有6個

 1 public Dictionary() : this(0, null) { }
 2 
 3 public Dictionary(int capacity) : this(capacity, null) { }
 4 
 5 public Dictionary(IEqualityComparer<TKey> comparer) : this(0, comparer) { }
 6 
 7 public Dictionary(int capacity, IEqualityComparer<TKey> comparer)  8 {  9     if (capacity < 0) throw new ArgumentOutOfRangeException(nameof(capacity), capacity, ""); 10     if (capacity > 0) Initialize(capacity); 11     this.comparer = comparer ?? EqualityComparer<TKey>.Default; 12 } 13 
14 public Dictionary(IDictionary<TKey, TValue> dictionary) : this(dictionary, null) { }
15 
16 public Dictionary(IDictionary<TKey, TValue> dictionary, IEqualityComparer<TKey> comparer) :
17     this(dictionary != null ? dictionary.Count : 0, comparer)
18 {
19     if (dictionary == null)
20     {
21         throw new ArgumentNullException(nameof(dictionary));
22     }
23     if (dictionary.GetType() == typeof(Dictionary<TKey, TValue>))
24     {
25         Dictionary<TKey, TValue> d = (Dictionary<TKey, TValue>)dictionary;
26         int count = d.count;
27         Entry[] entries = d.entries;
28         for (int i = 0; i < count; i++)
29         {
30             if (entries[i].hashCode >= 0)
31             {
32                 Add(entries[i].key, entries[i].value);
33             }
34         }
35         return;
36     }
37 
38     foreach (KeyValuePair<TKey, TValue> pair in dictionary)
39     {
40         Add(pair.Key, pair.Value);
41     }
42 }

大部分都是用默認值,真正用到的是public Dictionary(int capacity, IEqualityComparer<TKey> comparer),這個是每一個構造函數都要調用的,看看它作了什麼:

if (capacity > 0) Initialize(capacity); 當capacity大於0時,也就是顯示指定了capacity時纔會調用初始化函數,capacity指容量,List<T>裏也有說過,不一樣的是Dictionary只能在構造函數裏指定capacity,而List<T>能夠隨時指定。接下來看看初始化函數作了什麼:

1 private void Initialize(int capacity)
2 {
3     int size = HashHelpers.GetPrime(capacity);
4     buckets = new int[size];
5     for (int i = 0; i < buckets.Length; i++) buckets[i] = -1;
6     entries = new Entry[size];
7     freeList = -1;
8 }

HashHelpers.GetPrime(capacity)根據傳進來的capacity獲取一個質數,質數你們都知道 2,3,5,7,11,13等等除了自身和1,不能被其餘數整除的就是質數,具體看看這個獲取質數的函數:

 1 public static readonly int[] primes = {
 2     3, 7, 11, 17, 23, 29, 37, 47, 59, 71, 89, 107, 131, 163, 197, 239, 293, 353, 431, 521, 631, 761, 919,
 3     1103, 1327, 1597, 1931, 2333, 2801, 3371, 4049, 4861, 5839, 7013, 8419, 10103, 12143, 14591,
 4     17519, 21023, 25229, 30293, 36353, 43627, 52361, 62851, 75431, 90523, 108631, 130363, 156437,
 5     187751, 225307, 270371, 324449, 389357, 467237, 560689, 672827, 807403, 968897, 1162687, 1395263,
 6     1674319, 2009191, 2411033, 2893249, 3471899, 4166287, 4999559, 5999471, 7199369, 8639249, 10367101,
 7     12440537, 14928671, 17914409, 21497293, 25796759, 30956117, 37147349, 44576837, 53492207, 64190669,
 8     77028803, 92434613, 110921543, 133105859, 159727031, 191672443, 230006941, 276008387, 331210079,
 9     397452101, 476942527, 572331049, 686797261, 824156741, 988988137, 1186785773, 1424142949, 1708971541,
10     2050765853, MaxPrimeArrayLength };
11         
12 public static int GetPrime(int min)
13 {
14     if (min < 0)
15         throw new ArgumentException("");
16     Contract.EndContractBlock();
17 
18     for (int i = 0; i < primes.Length; i++)
19     {
20         int prime = primes[i];
21         if (prime >= min) return prime;
22     }
23 
24     return min;
25 }

這裏維護了個質數數組,注意,裏面並非完整的質數序列,而是有一些過濾掉了,由於有些挨着太緊,比方說2和3,增長一個就要擴容很不必。

GetPrime看if (prime >= min) return prime;這行代碼知道是要獲取第一個比傳進來的值大的質數,比方傳的是1,那3就是獲取到的初始容量。

接着看初始化部分的代碼:size如今知道是3,接下來以這個size來初始化buckets和entries,而且buckets裏的元素都設爲-1,freeList一樣初始化成-1,這個後面有用。

初始化完後再調用這行代碼 : this.comparer = comparer ?? EqualityComparer<TKey>.Default; 也是初始化comparer,看EqualityComparer<TKey>.Default這個到底用的是什麼:

 1 public static EqualityComparer<T> Default
 2 {
 3     get
 4     {
 5         if (_default == null)
 6         {
 7             object comparer;
 8                     
 9             if (typeof(T) == typeof(SByte))
10                 comparer = new EqualityComparerForSByte();
11             else if (typeof(T) == typeof(Byte))
12                 comparer = new EqualityComparerForByte();
13             else if (typeof(T) == typeof(Int16))
14                 comparer = new EqualityComparerForInt16();
15             else if (typeof(T) == typeof(UInt16))
16                 comparer = new EqualityComparerForUInt16();
17             else if (typeof(T) == typeof(Int32))
18                 comparer = new EqualityComparerForInt32();
19             else if (typeof(T) == typeof(UInt32))
20                 comparer = new EqualityComparerForUInt32();
21             else if (typeof(T) == typeof(Int64))
22                 comparer = new EqualityComparerForInt64();
23             else if (typeof(T) == typeof(UInt64))
24                 comparer = new EqualityComparerForUInt64();
25             else if (typeof(T) == typeof(IntPtr))
26                 comparer = new EqualityComparerForIntPtr();
27             else if (typeof(T) == typeof(UIntPtr))
28                 comparer = new EqualityComparerForUIntPtr();
29             else if (typeof(T) == typeof(Single))
30                 comparer = new EqualityComparerForSingle();
31             else if (typeof(T) == typeof(Double))
32                 comparer = new EqualityComparerForDouble();
33             else if (typeof(T) == typeof(Decimal))
34                 comparer = new EqualityComparerForDecimal();
35             else if (typeof(T) == typeof(String))
36                 comparer = new EqualityComparerForString();
37             else
38                 comparer = new LastResortEqualityComparer<T>();
39 
40             _default = (EqualityComparer<T>)comparer;
41         }
42 
43         return _default;
44     }
45 }

爲不一樣類型建立一個comparer,看下面代碼是咱們用到的string的comparer:hashcode直接取的string的hashcode,其實這裏面的全部類型取hashcode都是同樣,equals則有個別不一樣。

 1 internal sealed class EqualityComparerForString : EqualityComparer<String>
 2 {
 3     public override bool Equals(String x, String y)
 4     {
 5         return x == y;
 6     }
 7 
 8     public override int GetHashCode(String x)
 9     {
10         if (x == null)
11             return 0;
12         return x.GetHashCode();
13     }
14 }

基本構造函數就這些,還有個構造函數能夠傳一個IDictionary<TKey, TValue>進來,和List<T>同樣,也是初始化就加入這些集合,首先判斷是不是Dictionary,是的話直接遍歷它的entries,加到當前的entries裏,若是不是則用枚舉器遍歷。

爲何不直接用枚舉器呢,由於枚舉器也是要消耗一些資源的,並且沒有直接遍歷數組來得快。

這個構造函數添加時用到了Add方法,和例子裏Add同樣,正好是接下來要講的。

Add("a", "A")

下圖就是初始變量的狀態:

Add方法直接調用Insert方法,第三個參數爲true

1 public void Add(TKey key, TValue value)
2 {
3     Insert(key, value, true);
4 }

再看Insert方法,這個方法是核心方法,有點長,跟着註釋一點一點看。

 1 private void Insert(TKey key, TValue value, bool add)
 2 {
 3     if (key == null)
 4     {
 5         throw new ArgumentNullException(nameof(key));
 6     }
 7     //首先若是buckets爲空則初始化,第一次調用會走到這裏,以0爲capacity初始化,根據上面的分析,得到的初始容量是3,也就是說3是Dictionary<Tkey, TValue>的默認容量。
 8     if (buckets == null) Initialize(0); 
 9 
10     //取hashcode後還與0x7FFFFFFF作了個與操做,0x7FFFFFFF這就是int32.MaxValue的16進制,換成二進制是‭01111111111111111111111111111111‬,第1位是符號位,也就是說comparer.GetHashCode(key) 爲正數的狀況下與0x7FFFFFFF作 & 操做結果仍是它自己,若是取到的hashcode是負數,負數的二進制是取反再補碼,因此結果獲得的是0x7FFFFFFF-(-hashcode)+1,結果是正數。其實簡單來講,它的目的就是高性能的取正數。‬‬
11     int hashCode = comparer.GetHashCode(key) & 0x7FFFFFFF;
12 
13     //用獲得的新hashcode與buckets的大小取餘,獲得一個目標bucket索引
14     int targetBucket = hashCode % buckets.Length;
15 
16     //作個遍歷,初始值爲buckets[targetBucket],如今"a"的hashcode爲3,這樣targetBucket如今是0,buckets[0]是-1,i是要>=0的,循環走不下去,跳出
17     for (int i = buckets[targetBucket]; i >= 0; i = entries[i].next)
18     {
19         if (entries[i].hashCode == hashCode && comparer.Equals(entries[i].key, key))
20         {
21             if (add)
22             {
23                 throw new ArgumentException(SR.Format(SR.Argument_AddingDuplicate, key));
24             }
25             entries[i].value = value;
26             version++;
27             return;
28         }
29     }
30 
31     int index;
32     //freeCount也是-1,走到else裏面
33     if (freeCount > 0)
34     {
35         index = freeList;
36         freeList = entries[index].next;
37         freeCount--;
38     }
39     else
40     {
41         //count是元素的個數0, entries通過初始化後目前length是3,因此不用resize
42         if (count == entries.Length)
43         {
44             Resize();
45             targetBucket = hashCode % buckets.Length;
46         }
47         //index = count說明index指向entries數組裏當前要寫值的索引,目前是0
48         index = count;
49 
50         //元素個數增長一個
51         count++;
52     }
53 
54     //把key的hashcode存到entries[0]裏的hashcode,省得要用時重複計算hashcode
55     entries[index].hashCode = hashCode;
56     //entries[0]的next指向buckets[0]也就是-1
57     entries[index].next = buckets[targetBucket];
58     //設置key和value
59     entries[index].key = key;
60     entries[index].value = value;
61     //再讓buckets[0] = 0
62     buckets[targetBucket] = index;
63     //這個很少說,不知道的能夠看List<T>篇
64     version++;
65 }

看到這裏能夠先猜一下用bucket的目的,dictionary是爲了根據key快速獲得value,用key的hashcode來對長度取餘,取到的餘是0到(length-1)以前一個數,最好的狀況所有分散開,每一個key正好對應一個bucket,也就是entries裏每一項都對應一個bucket,就能夠造成下圖取value的過程:

這個取值過程很是快,由於沒有任何遍歷。但實際狀況是hashcode取的餘不會正好都不一樣,總有可能會有一些重複的,那這些重複的是怎麼處理的呢,仍是先繼續看Insert的代碼:

變量狀態以下圖:

從這圖能夠看出來是由hashcode獲得bucket的index(紫色線),而bucket的value是指向entry的index(黃色線), entry的next又指向bucket上一次的value(紅色線),是否是有鏈表的感受。

Add("b", "B")

因爲"b"的hashcode爲4,取餘得1,並無和現有的重複,因此流程和上面同樣(左邊的線不用看,屬於上面流程)

Add("c", "C")

"c"的hashcode是6,取餘得0,獲得也是在第0個bucket,這樣就產生碰撞了,

 1 for (int i = buckets[targetBucket]; i >= 0; i = entries[i].next)
 2 {
 3     if (entries[i].hashCode == hashCode && comparer.Equals(entries[i].key, key))
 4     {
 5         if (add)
 6         {
 7             throw new ArgumentException(SR.Format(SR.Argument_AddingDuplicate, key));
 8         }
 9         entries[i].value = value;
10         version++;
11         return;
12     }
13 }

這裏Insert函數裏就會走進for循環,不過"c"不是已經有的key,hashcode匹配不到因此if就不會進了。

狀態如圖:

從圖上看到,新添加的entry的index給到第0個bucket的value (黃色線),而bucket上一次的value(紅色線)也就是上次添加的元素的index給到新添加entry的next,這樣經過bucket獲得最新的entry,而不停的經過entry的next就能夠把同一個bucket下的entry都遍歷到。

dict["d"]="D" -> Resize()

再用索引器的方式加入"d",

1 public TValue this[TKey key]
2 {
3     set
4     {
5         Insert(key, value, false); 6     }
7 }

也是insert,不過第三個參數是false,這樣insert裏碰到相同的key會替換掉而不是像Add那樣拋異常,這個仍是不會走到if裏去,由於key不重複

 1 if (entries[i].hashCode == hashCode && comparer.Equals(entries[i].key, key))
 2 {
 3     if (add)  4     {
 5         throw new ArgumentException(SR.Format(SR.Argument_AddingDuplicate, key));
 6     }
 7     entries[i].value = value;
 8     version++;
 9     return;
10 }

不過因爲容量已經滿了,如今會走到下面這段代碼:

1 if (count == entries.Length)
2 {
3  Resize(); 4     targetBucket = hashCode % buckets.Length;
5 }

觸發Resize,看看Resize代碼:

1 private void Resize()
2 {
3     Resize(HashHelpers.ExpandPrime(count), false);
4 }

先經過HashHelpers.ExpandPrime(count)取到下個容量大小。

 1 public static int ExpandPrime(int oldSize)
 2 {
 3     int newSize = 2 * oldSize; //新size爲兩倍當前大小
 4     if ((uint)newSize > MaxPrimeArrayLength && MaxPrimeArrayLength > oldSize)//這裏MaxPrimeArrayLength是int32.MaxValue,size固然不能超過int32的最大值
 5     {
 6         Debug.Assert(MaxPrimeArrayLength == GetPrime(MaxPrimeArrayLength), "Invalid MaxPrimeArrayLength");
 7 
 8         return MaxPrimeArrayLength;
 9     }
10 
11     return GetPrime(newSize);//這個上面講過,是取比新size大的第一個質數
12 }

因此resize的容量不是2倍也不是上面那個質數數組日後找,而是比2倍大的第一個質數。那如今是3,2倍是6,下一個質數是7,擴容的目標是7。

再詳細看resize實現:

 1 private void Resize(int newSize, bool forceNewHashCodes)
 2 {
 3     Contract.Assert(newSize >= entries.Length);
 4     int[] newBuckets = new int[newSize];
 5     for (int i = 0; i < newBuckets.Length; i++) newBuckets[i] = -1;  //重置buckets
 6 
 7     Entry[] newEntries = new Entry[newSize];
 8     Array.Copy(entries, 0, newEntries, 0, count);  //創建新entries並把舊的entries複製進去
 9 
10     if (forceNewHashCodes) // 強制更新hashcode,dictionary不會走進去
11     {
12         for (int i = 0; i < count; i++)
13         {
14             if (newEntries[i].hashCode != -1)
15             {
16                 newEntries[i].hashCode = (comparer.GetHashCode(newEntries[i].key) & 0x7FFFFFFF);
17             }
18         }
19     }
20 
21     for (int i = 0; i < count; i++) //由於重置了buckets,因此這裏遍歷entries來從新創建bucket和entry的關係
22     {
23         if (newEntries[i].hashCode >= 0)  //hashcode作了正數處理,不該該都是大於0的麼,其實否則,remove裏講hashcode爲何會爲負
24         {
25             int bucket = newEntries[i].hashCode % newSize;
26             newEntries[i].next = newBuckets[bucket];
27             newBuckets[bucket] = i;  //仍是insert裏的那一套,同一個bucket index, bucket指向最新的entry的index, 而新entry的next就指向老的entry的index,循環下去
28         }
29     }
30 
31     buckets = newBuckets;
32     entries = newEntries;
33 }

由於大小變了,取餘也就不同,因此entry和bucket對應的位置也不一樣了,不過沒影響。

Resize消耗不低,比List<T>的要大,不光要copy元素,還要重建bucket。

Resize後繼續上面那一套,看狀態圖:

"d"的hashcode爲11,餘數是4(如今大小是7了哈),與"b"碰撞,因此next就指到"b"的index,而bucket則去記新添加的"d"了(典型的喜新厭舊,有沒有)。

dict["a"]="AA"

"a"已經添加過了,再次用索引器添加"a"就走了if裏面

 1 if (entries[i].hashCode == hashCode && comparer.Equals(entries[i].key, key))
 2 {
 3     if (add) //若是用Add方法會拋異常
 4     {
 5         throw new ArgumentException(SR.Format(SR.Argument_AddingDuplicate, key));
 6     }
 7     entries[i].value = value; //替換掉目標entry的值
 8     version++;
 9     return;  //這裏直接return了,由於只是替換值,與bucket關係並無改變
10 }

這步就很是之簡單,只是"A"替換成"AA"。

Remove("b")

來看看Remove代碼:

 1 public bool Remove(TKey key)
 2 {
 3     if (key == null)
 4     {
 5         throw new ArgumentNullException(nameof(key));
 6     }
 7 
 8     if (buckets != null)
 9     {
10         int hashCode = comparer.GetHashCode(key) & 0x7FFFFFFF;
11         int bucket = hashCode % buckets.Length;  //先算出hashcode
12         int last = -1;  //last初始爲-1
13         for (int i = buckets[bucket]; i >= 0; last = i, i = entries[i].next)  //last在循環時指向上一個entry的index
14         {
15             if (entries[i].hashCode == hashCode && comparer.Equals(entries[i].key, key)) //先找到相同的key
16             {
17                 if (last < 0)  //小於0說明是第1個,last只有初始爲-1
18                 {
19                     buckets[bucket] = entries[i].next; //remove第一個的話就只要把bucket的值指向要remove的entry的下一個就行了,這樣鏈表就繼續存在,只是把頭去掉了。
20                 }
21                 else
22                 {
23                     entries[last].next = entries[i].next;  //remove中間或最後的entry就讓上一個的next指向下一個的index,能夠想像在鏈表中間去掉一個,是否是得把上下兩邊再連起來
24                 }
25                 entries[i].hashCode = -1; //把hashcode置爲-1,上面有說hashcode有可能爲負,這裏就爲負數了
26                 entries[i].next = freeList;  //freeList在這裏用到了, 把刪除的entry的next指向freeList,如今爲-1
27                 entries[i].key = default(TKey); //key和value都設爲默認值,這裏由於是string因此都是null
28                 entries[i].value = default(TValue);
29                 freeList = i;  //freeList就指向這空出來的entry的index
30                 freeCount++;  //freeCount加一個,這裏能夠知道freeCount是用來記entries裏空出來的個數
31                 version++;
32                 return true;
33             }
34         }
35     }
36     return false;
37 }

這裏能夠看出Dictionary並不像List那樣Remove,Dictionary爲了性能並無在Remove作重建,而是把位置空出來,這樣節省大量時間。freeList和bucket相似(同樣喜新厭舊),老是指向最新空出來的entry的index,而entry的next又把全部空的entry連起來了。這樣insert時就能夠先找到這些空填進去。

這裏"d"的next原本是指向"b"的,Remove(b)後把"b"的next給了"d"(下面那條紅線),這樣繼續保持鏈表狀態。freeList和freeCount這裏就知道了是用來記住刪除元素的index和個數。

Add("e", "E")

這裏再添加一個,由於有空了,因此會優先補上空出來的。

1 if (freeCount > 0)  //freeCount大於0,因此進來了
2 {
3     index = freeList;  //當前index指向最新空出來的
4     freeList = entries[index].next;  //把freeList再指到下一個,保持鏈表
5     freeCount--;  //用掉一個少一個
6 }

"e"的hashcode爲10,因此也在index爲3的bucket裏,bucket value指向剛添加的entry也就是1,而這個entry的next就指向bucket舊的那個。這樣就把空出來的又補上了。

經過上面分析,對Dictionary添加和刪除的原理已經清楚了,這樣下面的也會很是容易理解。

var a = dict["a"]

來看看索引器的get

1 public TValue this[TKey key]
2 {
3     get
4     {
5         int i = FindEntry(key);
6         if (i >= 0) return entries[i].value;
7         throw new KeyNotFoundException();
8     }
9 }

是經過FindEntry來找到entry進而獲得value

 1 private int FindEntry(TKey key)
 2 {
 3     if (key == null)
 4     {
 5         throw new ArgumentNullException(nameof(key));
 6     }
 7 
 8     if (buckets != null)
 9     {
10         int hashCode = comparer.GetHashCode(key) & 0x7FFFFFFF;  //取hashcode
11         for (int i = buckets[hashCode % buckets.Length]; i >= 0; i = entries[i].next)  //遍歷bucket鏈表
12         {
13             if (entries[i].hashCode == hashCode && comparer.Equals(entries[i].key, key)) return i; //找到hashcode一致的,也就是一樣的key,返回entry索引
14         }
15     }
16     return -1;//沒找到key,後面就拋KeyNotFoundException了
17 }

var hasA = dict.ContainsKey("a")

看看ContainsKey代碼:

1 public bool ContainsKey(TKey key)
2 {
3     return FindEntry(key) >= 0;
4 }

和上面同樣,經過FindEntry來找索引,索引不爲-1就是包含。

其餘

看看Dictionary還有哪些值得注意的:

1 public int Count
2 {
3     get { return count - freeCount; }
4 }

真正的count是entries裏個數減去裏面空着的。

 1 public bool ContainsValue(TValue value)
 2 {
 3     if (value == null)
 4     {
 5         for (int i = 0; i < count; i++)
 6         {
 7             if (entries[i].hashCode >= 0 && entries[i].value == null) return true;
 8         }
 9     }
10     else
11     {
12         EqualityComparer<TValue> c = EqualityComparer<TValue>.Default;
13         for (int i = 0; i < count; i++)
14         {
15             if (entries[i].hashCode >= 0 && c.Equals(entries[i].value, value)) return true;
16         }
17     }
18     return false;
19 }

ContainsValue和ContainsKey就不同了,它沒有bucket能夠匹配,只能遍歷entries,因此性能和List的Contains同樣,使用時須要注意。

另外還有很多代碼是爲了實現Enumerator,畢竟Dictionary支持KeyValuePair, Key, Value三種方式遍歷,其實這三種遍歷都是對Entries數組的遍歷,這裏就很少作分析了。

總結

Dictionary的默認初始容量爲3,並在填滿時自動擴容,以比當前值的2倍大的第一個質數(固定質數數組裏的)做爲擴容目標。

Dictionary也不是線程安全,多線程環境下須要咱們本身加鎖,和List同樣也是經過version來確保遍歷時集合不被修改。

Dictionary的遍歷有三種,KeyValuePair,Key, Value,這三個本質都是遍歷entries數組。

Dictionary取值快速的原理是由於經過buckets來創建了Key與entry以前的聯繫,經過Key的hashcode算出bucket的index,而bucket的value指向entry的index,這樣快速獲得entry的value,固然也有不一樣的key指向同一個bucket,因此bucket的index老是指向最新的entry,而有衝突的entry又經過next鏈接,這樣即便有衝突也只要遍歷不多的entry就能夠取到值,Dictionary在元素越多時性能優點越明顯。

固然Dictionary爲取值快也是付出了一點小代價,就是經過空間換取時間,多加了buckets這個數組來創建key與entry的聯繫,另外還有entry結構裏的hashcode和next,不過相比速度這點代價基本能夠忽略了。

下面是上面例子的整個過程圖:(右鍵在新標籤頁打開)

相關文章
相關標籤/搜索