C#中Dictionary<TKey, TValue>的存储结构分析
<div><span style="font-family: 宋体, "Songti SC""> 无论是实际的项目中,还是在我们学习的过程中,都会重点的应用到Dictionary<TKey, TValue>这个存储类型。每次对Dictionary<TKey, TValue>的添加都包含一个值和与其关联的键, 使用键检索值的速度非常快,接近 O (1) ,因为 Dictionary<TKey, TValue> 类是作为哈希表实现的。</span><span style="font-family: 宋体, "Songti SC"">首先我们来从一个简单的例子开始,以下是对一个字典的创建和赋值。</span></div><div>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 128, 128, 1)">1</span> Dictionary<<span style="color: rgba(0, 0, 255, 1)">int</span>, <span style="color: rgba(0, 0, 255, 1)">string</span>> openWith = <span style="color: rgba(0, 0, 255, 1)">new</span> Dictionary<<span style="color: rgba(0, 0, 255, 1)">int</span>, <span style="color: rgba(0, 0, 255, 1)">string</span>><span style="color: rgba(0, 0, 0, 1)">();
</span><span style="color: rgba(0, 128, 128, 1)">2</span> openWith.Add(<span style="color: rgba(128, 0, 128, 1)">1000</span>, <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">key值为1000</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">);
</span><span style="color: rgba(0, 128, 128, 1)">3</span> openWith.Add(<span style="color: rgba(128, 0, 128, 1)">1001</span>, <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">key值为1001</span><span style="color: rgba(128, 0, 0, 1)">"</span>);</pre>
</div>
</div>
<div>
<div><span style="font-family: 宋体, "Songti SC""> 相信绝大部分的开发人员对以上示例不是会陌生,那么Dictionary<TKey, TValue>的实现原理是什么样的呢?在字典的初始化、赋值、取值、扩容的实现原理是什么样的呢?很多时候我们需要知其然,更需要知其所以然。接下来我们将从其内存的存储的数据结构、取值的逻辑、扩容原则等几个视角进行仔细的了解 。</span><span style="font-family: 宋体, "Songti SC""><span>那我们就沿着CoreFX中Dictionary<TKey, TValue>的实现源码来做一个简单的学习和思考,这里需要特别注意一下:</span></span></div>
<div>
<div><span style="font-family: 宋体, "Songti SC""> <span style="background-color: rgba(255, 255, 255, 1)">学习和分析源码时,不要先入为主,要按照框架和源码的逻辑进行解读,记录下不懂的地方重点分析,最后将整个逻辑串联起来。如果我们一开始就设定了</span></span><span style="background-color: rgba(255, 255, 255, 1)"><span style="font-family: 宋体, "Songti SC"">逻辑为A-B-C,但是读到一个阶段的时候发现变成了C-B-A,这个时候就无法再继续进行下去,因为具体的实现过程中会有很多因素造成局部调整,我们可以在解读</span><span style="font-family: 宋体, "Songti SC"">完毕之后,将实际的逻辑与个人前期理解的逻辑的差异进行比较,找出原因并做分析。</span></span></div>
</div>
<div><span style="font-family: 宋体, "Songti SC"; font-size: 14pt"><strong>一、Dictionary<TKey, TValue>初始化</strong></span></div>
<div><span style="font-family: 宋体, "Songti SC""> Dictionary<TKey, TValue>的构造方法较多,我们来看一下其中的基础实现方法,首先看一下对应的源码(源码中不必要的部分已经做了部分删减,保留了核心的实现逻辑)。</span></div>
<div>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 128, 128, 1)"> 1</span><span style="color: rgba(0, 0, 255, 1)">public</span> Dictionary(<span style="color: rgba(0, 0, 255, 1)">int</span> capacity, IEqualityComparer<TKey>?<span style="color: rgba(0, 0, 0, 1)"> comparer)
</span><span style="color: rgba(0, 128, 128, 1)"> 2</span> <span style="color: rgba(0, 0, 0, 1)">{
</span><span style="color: rgba(0, 128, 128, 1)"> 3</span> <span style="color: rgba(0, 0, 255, 1)">if</span> (capacity > <span style="color: rgba(128, 0, 128, 1)">0</span><span style="color: rgba(0, 0, 0, 1)">) Initialize(capacity);
</span><span style="color: rgba(0, 128, 128, 1)"> 4</span> <span style="color: rgba(0, 0, 255, 1)">if</span> (!<span style="color: rgba(0, 0, 255, 1)">typeof</span><span style="color: rgba(0, 0, 0, 1)">(TKey).IsValueType)
</span><span style="color: rgba(0, 128, 128, 1)"> 5</span> <span style="color: rgba(0, 0, 0, 1)"> {
</span><span style="color: rgba(0, 128, 128, 1)"> 6</span> _comparer = comparer ?? EqualityComparer<TKey><span style="color: rgba(0, 0, 0, 1)">.Default;
</span><span style="color: rgba(0, 128, 128, 1)"> 7</span> <span style="color: rgba(0, 0, 255, 1)">if</span> (<span style="color: rgba(0, 0, 255, 1)">typeof</span>(TKey) == <span style="color: rgba(0, 0, 255, 1)">typeof</span>(<span style="color: rgba(0, 0, 255, 1)">string</span>) && NonRandomizedStringEqualityComparer.GetStringComparer(_comparer!) <span style="color: rgba(0, 0, 255, 1)">is</span> IEqualityComparer<<span style="color: rgba(0, 0, 255, 1)">string</span>><span style="color: rgba(0, 0, 0, 1)"> stringComparer)
</span><span style="color: rgba(0, 128, 128, 1)"> 9</span> <span style="color: rgba(0, 0, 0, 1)"> {
</span><span style="color: rgba(0, 128, 128, 1)">10</span> _comparer = (IEqualityComparer<TKey><span style="color: rgba(0, 0, 0, 1)">)stringComparer;
</span><span style="color: rgba(0, 128, 128, 1)">11</span> <span style="color: rgba(0, 0, 0, 1)"> }
</span><span style="color: rgba(0, 128, 128, 1)">12</span> <span style="color: rgba(0, 0, 0, 1)"> }
</span><span style="color: rgba(0, 128, 128, 1)">13</span> <span style="color: rgba(0, 0, 255, 1)">else</span> <span style="color: rgba(0, 0, 255, 1)">if</span> (comparer <span style="color: rgba(0, 0, 255, 1)">is</span> not <span style="color: rgba(0, 0, 255, 1)">null</span> && comparer != EqualityComparer<TKey><span style="color: rgba(0, 0, 0, 1)">.Default)
</span><span style="color: rgba(0, 128, 128, 1)">14</span> <span style="color: rgba(0, 0, 0, 1)"> {
</span><span style="color: rgba(0, 128, 128, 1)">15</span> _comparer =<span style="color: rgba(0, 0, 0, 1)"> comparer;
</span><span style="color: rgba(0, 128, 128, 1)">16</span> <span style="color: rgba(0, 0, 0, 1)"> }
</span><span style="color: rgba(0, 128, 128, 1)">17</span> }</pre>
</div>
<div><span style="font-family: 宋体, "Songti SC""> 以上的实现逻辑重点包含了两个部分,第一部分:对Dictionary<TKey, TValue>的容量初始化;第二部分是Dictionary<TKey, TValue>的IEqualityComparer? comparer的初始化,本文重点是对Dictionary<TKey, TValue>的存储结构进行分析,涉及到比较器的实现逻辑,将放在后续的章节中进行重点介绍。</span></div>
<div><span style="font-family: 宋体, "Songti SC""> 我们接下来看一下Initialize()的实现逻辑进行一个简单的介绍,首先一起来看一下对应的源码实现(非必要部分已做删减,方便大家可以直观的查看)。</span></div>
<div>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 128, 128, 1)"> 1</span> <span style="color: rgba(0, 0, 255, 1)">private</span> <span style="color: rgba(0, 0, 255, 1)">int</span> Initialize(<span style="color: rgba(0, 0, 255, 1)">int</span><span style="color: rgba(0, 0, 0, 1)"> capacity)
</span><span style="color: rgba(0, 128, 128, 1)"> 2</span> <span style="color: rgba(0, 0, 0, 1)">{
</span><span style="color: rgba(0, 128, 128, 1)"> 3</span> <span style="color: rgba(0, 0, 255, 1)">int</span> size =<span style="color: rgba(0, 0, 0, 1)"> HashHelpers.GetPrime(capacity);
</span><span style="color: rgba(0, 128, 128, 1)"> 4</span> <span style="color: rgba(0, 0, 255, 1)">int</span>[] buckets = <span style="color: rgba(0, 0, 255, 1)">new</span> <span style="color: rgba(0, 0, 255, 1)">int</span><span style="color: rgba(0, 0, 0, 1)">;
</span><span style="color: rgba(0, 128, 128, 1)"> 5</span> Entry[] entries = <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> Entry;
</span><span style="color: rgba(0, 128, 128, 1)"> 6</span> _freeList = -<span style="color: rgba(128, 0, 128, 1)">1</span><span style="color: rgba(0, 0, 0, 1)">;
</span><span style="color: rgba(0, 128, 128, 1)"> 7</span> <span style="color: rgba(0, 0, 255, 1)">#if</span> TARGET_64BIT
<span style="color: rgba(0, 128, 128, 1)"> 8</span> _fastModMultiplier = HashHelpers.GetFastModMultiplier((<span style="color: rgba(0, 0, 255, 1)">uint</span><span style="color: rgba(0, 0, 0, 1)">)size);
</span><span style="color: rgba(0, 128, 128, 1)"> 9</span> <span style="color: rgba(0, 0, 255, 1)">#endif</span>
<span style="color: rgba(0, 128, 128, 1)">10</span> _buckets =<span style="color: rgba(0, 0, 0, 1)"> buckets;
</span><span style="color: rgba(0, 128, 128, 1)">11</span> _entries =<span style="color: rgba(0, 0, 0, 1)"> entries;
</span><span style="color: rgba(0, 128, 128, 1)">12</span> <span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> size;
</span><span style="color: rgba(0, 128, 128, 1)">13</span> } </pre>
</div>
<div><span style="font-family: 宋体, "Songti SC""> 从上面的源码可以看出,根据传入的capacity参数来设定字典对应的相关容量大小,其中包含两部分,第一部分: 根据设定的容量(capacity)大小,计算对应的buckets和entries大小,关于为什么使用buckets和entries两个数组结构,我们将在下一节重点介绍;第二部分:判断当前机器的位数,计算对应的_fastModMultiplier。我们看一下HashHelpers.GetPrime(capacity)的计算逻辑。(该类在System.Collections命名空间下,其对应的类型定义为:internal static partial class HashHelpers)</span></div>
<div>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 128, 128, 1)"> 1</span> <span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">static</span> <span style="color: rgba(0, 0, 255, 1)">int</span> GetPrime(<span style="color: rgba(0, 0, 255, 1)">int</span><span style="color: rgba(0, 0, 0, 1)"> min)
</span><span style="color: rgba(0, 128, 128, 1)"> 2</span> <span style="color: rgba(0, 0, 0, 1)">{
</span><span style="color: rgba(0, 128, 128, 1)"> 3</span> <span style="color: rgba(0, 0, 255, 1)">foreach</span> (<span style="color: rgba(0, 0, 255, 1)">int</span> prime <span style="color: rgba(0, 0, 255, 1)">in</span><span style="color: rgba(0, 0, 0, 1)"> Primes)
</span><span style="color: rgba(0, 128, 128, 1)"> 4</span> <span style="color: rgba(0, 0, 0, 1)">{
</span><span style="color: rgba(0, 128, 128, 1)"> 5</span> <span style="color: rgba(0, 0, 255, 1)">if</span> (prime >= min) <span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> prime;
</span><span style="color: rgba(0, 128, 128, 1)"> 6</span> <span style="color: rgba(0, 0, 255, 1)">for</span> (<span style="color: rgba(0, 0, 255, 1)">int</span> i = (min | <span style="color: rgba(128, 0, 128, 1)">1</span>); i < <span style="color: rgba(0, 0, 255, 1)">int</span>.MaxValue; i += <span style="color: rgba(128, 0, 128, 1)">2</span><span style="color: rgba(0, 0, 0, 1)">)
</span><span style="color: rgba(0, 128, 128, 1)"> 7</span> <span style="color: rgba(0, 0, 0, 1)"> {
</span><span style="color: rgba(0, 128, 128, 1)"> 8</span> <span style="color: rgba(0, 0, 255, 1)">if</span> (IsPrime(i) && ((i - <span style="color: rgba(128, 0, 128, 1)">1</span>) % HashPrime != <span style="color: rgba(128, 0, 128, 1)">0</span>)) <span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> i;
</span><span style="color: rgba(0, 128, 128, 1)"> 9</span> <span style="color: rgba(0, 0, 0, 1)"> }
</span><span style="color: rgba(0, 128, 128, 1)">10</span> <span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> min;
</span><span style="color: rgba(0, 128, 128, 1)">11</span> <span style="color: rgba(0, 0, 0, 1)"> }
</span><span style="color: rgba(0, 128, 128, 1)">12</span> }</pre>
</div>
<div><span style="font-family: 宋体, "Songti SC""> HashHelpers用于计算和维护哈希表容量的素数值,为什么哈希表需要使用素数?主要是为了减少哈希冲突(hash collisions)的发生,素数的选择能够减少共同的因子,减小哈希冲突的可能性。此外,选择素数还能够确保在哈希表的容量变化时,不容易出现过多的重复。如果容量选择为一个合数(非素数),那么在容量变化时,可能会导致新容量与旧容量有相同的因子,增加哈希冲突的风险。</span></div>
<div><span style="font-family: 宋体, "Songti SC""> 接下来我们沿着GetPrime()的调用关系来看整个哈希表容量的计算逻辑,HashHelpers设定了一个Primes[]的只读素数数组,具体的元素如下,至于什么使用这样的素数的数组,主要是这些素数在实践中已经被证明是有效的,适用于许多常见的使用场景,更多的是有助于在哈希表等数据结构中提供更好的性能。</span></div>
<div>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 128, 128, 1)">1</span> <span style="color: rgba(0, 0, 255, 1)">internal</span> <span style="color: rgba(0, 0, 255, 1)">static</span> ReadOnlySpan<<span style="color: rgba(0, 0, 255, 1)">int</span>> Primes => <span style="color: rgba(0, 0, 255, 1)">new</span> <span style="color: rgba(0, 0, 255, 1)">int</span><span style="color: rgba(0, 0, 0, 1)">[]
</span><span style="color: rgba(0, 128, 128, 1)">2</span> <span style="color: rgba(0, 0, 0, 1)">{
</span><span style="color: rgba(0, 128, 128, 1)">3</span> <span style="color: rgba(128, 0, 128, 1)">3</span>, <span style="color: rgba(128, 0, 128, 1)">7</span>, <span style="color: rgba(128, 0, 128, 1)">11</span>, <span style="color: rgba(128, 0, 128, 1)">17</span>, <span style="color: rgba(128, 0, 128, 1)">23</span>, <span style="color: rgba(128, 0, 128, 1)">29</span>, <span style="color: rgba(128, 0, 128, 1)">37</span>, <span style="color: rgba(128, 0, 128, 1)">47</span>, <span style="color: rgba(128, 0, 128, 1)">59</span>, <span style="color: rgba(128, 0, 128, 1)">71</span>, <span style="color: rgba(128, 0, 128, 1)">89</span>, <span style="color: rgba(128, 0, 128, 1)">107</span>, <span style="color: rgba(128, 0, 128, 1)">131</span>, <span style="color: rgba(128, 0, 128, 1)">163</span>, <span style="color: rgba(128, 0, 128, 1)">197</span>, <span style="color: rgba(128, 0, 128, 1)">239</span>, <span style="color: rgba(128, 0, 128, 1)">293</span>, <span style="color: rgba(128, 0, 128, 1)">353</span>, <span style="color: rgba(128, 0, 128, 1)">431</span>, <span style="color: rgba(128, 0, 128, 1)">521</span>, <span style="color: rgba(128, 0, 128, 1)">631</span>, <span style="color: rgba(128, 0, 128, 1)">761</span>, <span style="color: rgba(128, 0, 128, 1)">919</span><span style="color: rgba(0, 0, 0, 1)">,
</span><span style="color: rgba(0, 128, 128, 1)">4</span> <span style="color: rgba(128, 0, 128, 1)">1103</span>, <span style="color: rgba(128, 0, 128, 1)">1327</span>, <span style="color: rgba(128, 0, 128, 1)">1597</span>, <span style="color: rgba(128, 0, 128, 1)">1931</span>, <span style="color: rgba(128, 0, 128, 1)">2333</span>, <span style="color: rgba(128, 0, 128, 1)">2801</span>, <span style="color: rgba(128, 0, 128, 1)">3371</span>, <span style="color: rgba(128, 0, 128, 1)">4049</span>, <span style="color: rgba(128, 0, 128, 1)">4861</span>, <span style="color: rgba(128, 0, 128, 1)">5839</span>, <span style="color: rgba(128, 0, 128, 1)">7013</span>, <span style="color: rgba(128, 0, 128, 1)">8419</span>, <span style="color: rgba(128, 0, 128, 1)">10103</span>, <span style="color: rgba(128, 0, 128, 1)">12143</span>, <span style="color: rgba(128, 0, 128, 1)">14591</span><span style="color: rgba(0, 0, 0, 1)">,
</span><span style="color: rgba(0, 128, 128, 1)">5</span> <span style="color: rgba(128, 0, 128, 1)">17519</span>, <span style="color: rgba(128, 0, 128, 1)">21023</span>, <span style="color: rgba(128, 0, 128, 1)">25229</span>, <span style="color: rgba(128, 0, 128, 1)">30293</span>, <span style="color: rgba(128, 0, 128, 1)">36353</span>, <span style="color: rgba(128, 0, 128, 1)">43627</span>, <span style="color: rgba(128, 0, 128, 1)">52361</span>, <span style="color: rgba(128, 0, 128, 1)">62851</span>, <span style="color: rgba(128, 0, 128, 1)">75431</span>, <span style="color: rgba(128, 0, 128, 1)">90523</span>, <span style="color: rgba(128, 0, 128, 1)">108631</span>, <span style="color: rgba(128, 0, 128, 1)">130363</span>, <span style="color: rgba(128, 0, 128, 1)">156437</span><span style="color: rgba(0, 0, 0, 1)">,
</span><span style="color: rgba(0, 128, 128, 1)">6</span> <span style="color: rgba(128, 0, 128, 1)">187751</span>, <span style="color: rgba(128, 0, 128, 1)">225307</span>, <span style="color: rgba(128, 0, 128, 1)">270371</span>, <span style="color: rgba(128, 0, 128, 1)">324449</span>, <span style="color: rgba(128, 0, 128, 1)">389357</span>, <span style="color: rgba(128, 0, 128, 1)">467237</span>, <span style="color: rgba(128, 0, 128, 1)">560689</span>, <span style="color: rgba(128, 0, 128, 1)">672827</span>, <span style="color: rgba(128, 0, 128, 1)">807403</span>, <span style="color: rgba(128, 0, 128, 1)">968897</span>, <span style="color: rgba(128, 0, 128, 1)">1162687</span>, <span style="color: rgba(128, 0, 128, 1)">1395263</span><span style="color: rgba(0, 0, 0, 1)">,
</span><span style="color: rgba(0, 128, 128, 1)">7</span> <span style="color: rgba(128, 0, 128, 1)">1674319</span>, <span style="color: rgba(128, 0, 128, 1)">2009191</span>, <span style="color: rgba(128, 0, 128, 1)">2411033</span>, <span style="color: rgba(128, 0, 128, 1)">2893249</span>, <span style="color: rgba(128, 0, 128, 1)">3471899</span>, <span style="color: rgba(128, 0, 128, 1)">4166287</span>, <span style="color: rgba(128, 0, 128, 1)">4999559</span>, <span style="color: rgba(128, 0, 128, 1)">5999471</span>, <span style="color: rgba(128, 0, 128, 1)">7199369</span>
<span style="color: rgba(0, 128, 128, 1)">8</span> };</pre>
</div>
<div><span style="font-family: 宋体, "Songti SC""> GetPrime()会首先循环Primes[],依次判断设定的min大小与素数表元素的关系,若素数表中的元素大于min,则直接去对应的素数,无需后续的计算,如果设置的min不在预定的素数表中,则进行素数的计算。关于素数的计算逻辑,借助本文开头的Dictionary<TKey, TValue>的定义和赋值进行说明,</span><span style="font-family: 宋体, "Songti SC"">首先对min和1进行按位或运算,初始化过程中未对capacity赋值时,则(min | 1)为1,对进行位运算后的i值校验是否符合素数定义,再进行((i - 1) % HashPrime != 0)运算,其中HashPrime = 101,用于在哈希算法中作为质数因子(101是一个相对小的质数,可以减少哈希碰撞的可能性,并且在计算哈希时更加高效),对于初始化未设置容量的Dictionary<TKey, TValue>,计算获取得到的容量为int size=3。(即3*4*8=72(bit))</span></div>
<div><span style="font-family: 宋体, "Songti SC""> (注意:对于已设定了capacity的Dictionary,按照以上的逻辑进行计算对应的size值。这里就不再做过多介绍)</span></div>
<div><span style="font-family: 宋体, "Songti SC""> 计算获取到size值后,设置空闲列表为-1(_freeList = -1)。根据编译时的运行机器的位数进行分类处理,若机器为非64位,则对buckets和entries两个数组进行初始化。若机器为64位是,则需要进行重新计算,获取_fastModMultiplier,其计算逻辑如下:</span></div>
<div>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">static</span> <span style="color: rgba(0, 0, 255, 1)">ulong</span> GetFastModMultiplier(<span style="color: rgba(0, 0, 255, 1)">uint</span> divisor) => <span style="color: rgba(0, 0, 255, 1)">ulong</span>.MaxValue / divisor + <span style="color: rgba(128, 0, 128, 1)">1</span>;</pre>
</div>
<div><span style="font-family: 宋体, "Songti SC""> 以上的计算结果返回除数的近似倒数,计算用于快速取模运算的乘法因子。</span></div>
<div><span style="font-family: 宋体, "Songti SC""> 通过以上的计算过程,我们可以对Dictionary<TKey, TValue>的容量计算有一个简单的认识,接下来我们来具体看一下用于存储数据和哈希索引的两个数组。</span></div>
<div>
<div><span style="font-family: 宋体, "Songti SC"; font-size: 14pt"><strong>二、Dictionary<TKey, TValue>的存储基础结构</strong></span></div>
<div><span style="font-family: 宋体, "Songti SC""> 对于Dictionary<TKey, TValue>的两个重要数组buckets和entries,我们来具体的分析一下。首先来看一下Entry[]?_entries的实际的数据结构:</span></div>
<div>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 128, 128, 1)">1</span> <span style="color: rgba(0, 0, 255, 1)">private</span> <span style="color: rgba(0, 0, 255, 1)">struct</span><span style="color: rgba(0, 0, 0, 1)"> Entry
</span><span style="color: rgba(0, 128, 128, 1)">2</span> <span style="color: rgba(0, 0, 0, 1)">{
</span><span style="color: rgba(0, 128, 128, 1)">3</span> <span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">uint</span><span style="color: rgba(0, 0, 0, 1)"> hashCode;
</span><span style="color: rgba(0, 128, 128, 1)">4</span> <span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">int</span><span style="color: rgba(0, 0, 0, 1)"> next;
</span><span style="color: rgba(0, 128, 128, 1)">5</span> <span style="color: rgba(0, 0, 255, 1)">public</span><span style="color: rgba(0, 0, 0, 1)"> TKey key;
</span><span style="color: rgba(0, 128, 128, 1)">6</span> <span style="color: rgba(0, 0, 255, 1)">public</span><span style="color: rgba(0, 0, 0, 1)"> TValue value;
</span><span style="color: rgba(0, 128, 128, 1)">7</span> }</pre>
</div>
<div><span style="font-family: 宋体, "Songti SC""> 在Dictionary<TKey, TValue>中实际存储数据的结构是Entry[],其中数组的每个元素是一个Entry,该类型为一个结构体,用于在哈希表内部存储每个键值对的信息,其中定义的key和value则是我们在设置字典时添加的键值对,那么对于另外两个属性需要重点分析一下。</span></div>
<div><span style="font-family: 宋体, "Songti SC""> hashCode为在添加key时,将key进行计算获取得到的哈希值,哈希值的计算过程中,需要对key进行按类别进行计算,C#中对数值类型、字符串、结构体、对象的哈希值计算逻辑都不相同,其中对于"数值类型"的哈希值计算逻辑为"数字类型的哈希码生成逻辑通常是将数字类型的值转换为整数,然后将该整数作为哈希码。"对于字符串的哈希值计算逻辑为"默认的字符串哈希码计算方式采用了所谓的“Jenkins One-at-a-Time Hash”算法的变体。"对于结构体和对象的哈希值计算逻辑就不做具体介绍。</span></div>
<div><span style="font-family: 宋体, "Songti SC""> next通常用于处理哈希冲突,即多个键具有相同的哈希码的情况。next是一个索引,指向哈希表中下一个具有相同哈希码的元素。其中next=-1时,表示链表结束;next=-2 表示空闲列表的末尾,next=-3 表示在空闲列表上的索引 0,next=-4 表示在空闲列表上的索引 1,后续则依次类推。</span></div>
<div><span style="font-family: 宋体, "Songti SC""> Entry通过使用结构体而不是类,可以减少内存开销,因为结构体是值类型,而类是引用类型。结构体在栈上分配,而类在堆上分配。</span></div>
<div><span style="font-family: 宋体, "Songti SC""> 以上介绍了Entry的结构和对应的属性字段,接下来我们再来看一下int[] buckets的结构和计算逻辑,buckets是一个简单的int类型的数组,这样的数组通常用于存储哈希桶的信息。每个桶实际上是一个索引,指向一个链表或链表的头部,用于解决哈希冲突。</span></div>
<div>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 128, 128, 1)">1</span><span style="color: rgba(0, 0, 255, 1)">private</span> <span style="color: rgba(0, 0, 255, 1)">ref</span> <span style="color: rgba(0, 0, 255, 1)">int</span> GetBucket(<span style="color: rgba(0, 0, 255, 1)">uint</span><span style="color: rgba(0, 0, 0, 1)"> hashCode)
</span><span style="color: rgba(0, 128, 128, 1)">2</span> <span style="color: rgba(0, 0, 0, 1)">{
</span><span style="color: rgba(0, 128, 128, 1)">3</span> <span style="color: rgba(0, 0, 255, 1)">int</span>[] buckets = _buckets!<span style="color: rgba(0, 0, 0, 1)">;
</span><span style="color: rgba(0, 128, 128, 1)">4</span><span style="color: rgba(0, 0, 255, 1)">#if</span> TARGET_64BIT
<span style="color: rgba(0, 128, 128, 1)">5</span> <span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(0, 0, 255, 1)">ref</span> buckets;
</span><span style="color: rgba(0, 128, 128, 1)">6</span><span style="color: rgba(0, 0, 255, 1)">#else</span>
<span style="color: rgba(0, 128, 128, 1)">7</span> <span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(0, 0, 255, 1)">ref</span> buckets[(<span style="color: rgba(0, 0, 255, 1)">uint</span>)hashCode %<span style="color: rgba(0, 0, 0, 1)"> buckets.Length];
</span><span style="color: rgba(0, 128, 128, 1)">8</span><span style="color: rgba(0, 0, 255, 1)">#endif</span>
<span style="color: rgba(0, 128, 128, 1)">9</span>} </pre>
</div>
</div>
<div>
<div><span style="font-family: 宋体, "Songti SC""> GetBucket()用于在哈希表中获取桶索引,其中参数hashCode为key对应的哈希码,在64位目标体系结构下,使用 HashHelpers.FastMod 方法进行快速模运算,而在32位目标体系结构下,使用普通的取模运算。那么为什么在Dictionary<TKey, TValue>中维护一个用来存储哈希表的桶呢?主要有以下4个目的:</span></div>
<div><span style="font-family: 宋体, "Songti SC""> (1)、解决哈希冲突:两个或多个不同的键经过哈希函数得到相同的哈希码,导致它们应该存储在哈希表的相同位置。通过使用桶,可以在同一个位置存储多个元素,解决了哈希冲突的问题。</span></div>
<div><span style="font-family: 宋体, "Songti SC""> (2)、提供快速查找:通过哈希函数计算键的哈希码,然后将元素存储在哈希表的桶中,可以在常数时间内(平均情况下)定位到存储该元素的位置,实现快速的查找。</span></div>
<div><span style="font-family: 宋体, "Songti SC""> (3)、支持高效的插入和删除:当插入元素时,通过哈希函数确定元素应该存储的桶,然后将其添加到桶的链表或其他数据结构中。当删除元素时,同样可以快速定位到存储元素的桶,并删除该元素。</span></div>
<div><span style="font-family: 宋体, "Songti SC""> (4)、平衡负载:哈希表的性能与负载因子相关,而负载因子是元素数量与桶数量的比值。使用适当数量的桶可以帮助平衡负载,防止哈希表变得过度拥挤,从而保持其性能。在不同的哈希表实现可能使用不同的数据结构,如链表、树等,C#的Dictionary中使用一个int[]维护这个哈希表的桶索引。</span></div>
<div><span style="font-family: 宋体, "Songti SC"; font-size: 14pt"><strong>三、Dictionary<TKey, TValue>的TryAdd的实现方式</strong></span></div>
<div><span style="font-family: 宋体, "Songti SC""> 以上主要介绍了Dictionary<TKey, TValue>的初始化、数据对应的存储和哈希表桶索引的存储结构,现在我们具体看一下Dictionary<TKey, TValue>的添加元素的实现方式,下面对C#的实现代码进行了精简,删除当前并不关注的部分。</span></div>
<div><span style="font-family: 宋体, "Songti SC""> 本文实例中对key赋值的为整数类型,部分对于非数值类型、调试代码等进行删减。(由于对于对象或者设置了比较器逻辑相对繁琐,将在下文中进行介绍)</span></div>
<div>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">private</span> <span style="color: rgba(0, 0, 255, 1)">bool</span><span style="color: rgba(0, 0, 0, 1)"> TryInsert(TKey key, TValue value, InsertionBehavior behavior)
{
Entry[]</span>? entries =<span style="color: rgba(0, 0, 0, 1)"> _entries;
</span><span style="color: rgba(0, 0, 255, 1)">uint</span> hashCode = (<span style="color: rgba(0, 0, 255, 1)">uint</span><span style="color: rgba(0, 0, 0, 1)">) key.GetHashCode() ;
</span><span style="color: rgba(0, 0, 255, 1)">uint</span> collisionCount = <span style="color: rgba(128, 0, 128, 1)">0</span><span style="color: rgba(0, 0, 0, 1)">;
</span><span style="color: rgba(0, 0, 255, 1)">ref</span> <span style="color: rgba(0, 0, 255, 1)">int</span> bucket = <span style="color: rgba(0, 0, 255, 1)">ref</span><span style="color: rgba(0, 0, 0, 1)"> GetBucket(hashCode);
</span><span style="color: rgba(0, 0, 255, 1)">int</span> i = bucket - <span style="color: rgba(128, 0, 128, 1)">1</span><span style="color: rgba(0, 0, 0, 1)">;
</span><span style="color: rgba(0, 0, 255, 1)">int</span><span style="color: rgba(0, 0, 0, 1)"> index;
</span><span style="color: rgba(0, 0, 255, 1)">if</span> (_freeCount > <span style="color: rgba(128, 0, 128, 1)">0</span><span style="color: rgba(0, 0, 0, 1)">)
{
index </span>=<span style="color: rgba(0, 0, 0, 1)"> _freeList;
_freeList </span>= StartOfFreeList -<span style="color: rgba(0, 0, 0, 1)"> entries.next;
_freeCount</span>--<span style="color: rgba(0, 0, 0, 1)">;
}
</span><span style="color: rgba(0, 0, 255, 1)">else</span><span style="color: rgba(0, 0, 0, 1)">
{
</span><span style="color: rgba(0, 0, 255, 1)">int</span> count =<span style="color: rgba(0, 0, 0, 1)"> _count;
</span><span style="color: rgba(0, 0, 255, 1)">if</span> (count ==<span style="color: rgba(0, 0, 0, 1)"> entries.Length)
{
Resize();
bucket </span>= <span style="color: rgba(0, 0, 255, 1)">ref</span><span style="color: rgba(0, 0, 0, 1)"> GetBucket(hashCode);
}
index </span>=<span style="color: rgba(0, 0, 0, 1)"> count;
_count </span>= count + <span style="color: rgba(128, 0, 128, 1)">1</span><span style="color: rgba(0, 0, 0, 1)">;
entries </span>=<span style="color: rgba(0, 0, 0, 1)"> _entries;
}
</span><span style="color: rgba(0, 0, 255, 1)">ref</span> Entry entry = <span style="color: rgba(0, 0, 255, 1)">ref</span> entries!<span style="color: rgba(0, 0, 0, 1)">;
entry.hashCode </span>=<span style="color: rgba(0, 0, 0, 1)"> hashCode;
entry.next </span>= bucket - <span style="color: rgba(128, 0, 128, 1)">1</span><span style="color: rgba(0, 0, 0, 1)">;
entry.key </span>=<span style="color: rgba(0, 0, 0, 1)"> key;
entry.value </span>=<span style="color: rgba(0, 0, 0, 1)"> value;
bucket </span>= index + <span style="color: rgba(128, 0, 128, 1)">1</span><span style="color: rgba(0, 0, 0, 1)">;
_version</span>++<span style="color: rgba(0, 0, 0, 1)">;
</span><span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(0, 0, 255, 1)">true</span><span style="color: rgba(0, 0, 0, 1)">;
}</span></pre>
</div>
<div><span style="font-family: 宋体, "Songti SC""> 以上的源码中的实现逻辑中核心包含3个部分,分别是计算hashCode、计算哈希表桶索引的bucket、Dictionary扩容,上一节中已经介绍了前两个实现逻辑,本节重点介绍Dictionary<TKey, TValue>的扩容逻辑,我们来看一下Resize()的实现逻辑。</span></div>
<div>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 128, 128, 1)"> 1</span> <span style="color: rgba(0, 0, 255, 1)">private</span> <span style="color: rgba(0, 0, 255, 1)">void</span> Resize() => Resize(HashHelpers.ExpandPrime(_count), <span style="color: rgba(0, 0, 255, 1)">false</span><span style="color: rgba(0, 0, 0, 1)">);
</span><span style="color: rgba(0, 128, 128, 1)"> 2</span>
<span style="color: rgba(0, 128, 128, 1)"> 3</span> <span style="color: rgba(0, 0, 255, 1)">private</span> <span style="color: rgba(0, 0, 255, 1)">void</span> Resize(<span style="color: rgba(0, 0, 255, 1)">int</span> newSize, <span style="color: rgba(0, 0, 255, 1)">bool</span><span style="color: rgba(0, 0, 0, 1)"> forceNewHashCodes)
</span><span style="color: rgba(0, 128, 128, 1)"> 4</span> <span style="color: rgba(0, 0, 0, 1)">{
</span><span style="color: rgba(0, 128, 128, 1)"> 5</span> Entry[] entries = <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> Entry;
</span><span style="color: rgba(0, 128, 128, 1)"> 6</span> <span style="color: rgba(0, 0, 255, 1)">int</span> count =<span style="color: rgba(0, 0, 0, 1)"> _count;
</span><span style="color: rgba(0, 128, 128, 1)"> 7</span> <span style="color: rgba(0, 0, 0, 1)"> Array.Copy(_entries, entries, count);
</span><span style="color: rgba(0, 128, 128, 1)"> 8</span> _buckets = <span style="color: rgba(0, 0, 255, 1)">new</span> <span style="color: rgba(0, 0, 255, 1)">int</span><span style="color: rgba(0, 0, 0, 1)">;
</span><span style="color: rgba(0, 128, 128, 1)"> 9</span> <span style="color: rgba(0, 0, 255, 1)">#if</span> TARGET_64BIT
<span style="color: rgba(0, 128, 128, 1)">10</span> _fastModMultiplier = HashHelpers.GetFastModMultiplier((<span style="color: rgba(0, 0, 255, 1)">uint</span><span style="color: rgba(0, 0, 0, 1)">)newSize);
</span><span style="color: rgba(0, 128, 128, 1)">11</span> <span style="color: rgba(0, 0, 255, 1)">#endif</span>
<span style="color: rgba(0, 128, 128, 1)">12</span> <span style="color: rgba(0, 0, 255, 1)">for</span> (<span style="color: rgba(0, 0, 255, 1)">int</span> i = <span style="color: rgba(128, 0, 128, 1)">0</span>; i < count; i++<span style="color: rgba(0, 0, 0, 1)">)
</span><span style="color: rgba(0, 128, 128, 1)">13</span> <span style="color: rgba(0, 0, 0, 1)"> {
</span><span style="color: rgba(0, 128, 128, 1)">14</span> <span style="color: rgba(0, 0, 255, 1)">if</span> (entries.next >= -<span style="color: rgba(128, 0, 128, 1)">1</span><span style="color: rgba(0, 0, 0, 1)">)
</span><span style="color: rgba(0, 128, 128, 1)">15</span> <span style="color: rgba(0, 0, 0, 1)"> {
</span><span style="color: rgba(0, 128, 128, 1)">16</span> <span style="color: rgba(0, 0, 255, 1)">ref</span> <span style="color: rgba(0, 0, 255, 1)">int</span> bucket = <span style="color: rgba(0, 0, 255, 1)">ref</span><span style="color: rgba(0, 0, 0, 1)"> GetBucket(entries.hashCode);
</span><span style="color: rgba(0, 128, 128, 1)">17</span> entries.next = bucket - <span style="color: rgba(128, 0, 128, 1)">1</span><span style="color: rgba(0, 0, 0, 1)">;
</span><span style="color: rgba(0, 128, 128, 1)">18</span> bucket = i + <span style="color: rgba(128, 0, 128, 1)">1</span><span style="color: rgba(0, 0, 0, 1)">;
</span><span style="color: rgba(0, 128, 128, 1)">19</span> <span style="color: rgba(0, 0, 0, 1)"> }
</span><span style="color: rgba(0, 128, 128, 1)">20</span> <span style="color: rgba(0, 0, 0, 1)"> }
</span><span style="color: rgba(0, 128, 128, 1)">21</span> _entries =<span style="color: rgba(0, 0, 0, 1)"> entries;
</span><span style="color: rgba(0, 128, 128, 1)">22</span> } </pre>
</div>
<div><span style="font-family: 宋体, "Songti SC""> 由以上的源码(不涉及数值类型的部分做了删减)可以看出,HashHelpers.ExpandPrime(_count)计算新的Entry[]大小,那我们来具体看一下这个新的数组大小的计算逻辑是如何实现的。</span></div>
<div>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 128, 128, 1)">1</span> <span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">static</span> <span style="color: rgba(0, 0, 255, 1)">int</span> ExpandPrime(<span style="color: rgba(0, 0, 255, 1)">int</span><span style="color: rgba(0, 0, 0, 1)"> oldSize)
</span><span style="color: rgba(0, 128, 128, 1)">2</span> <span style="color: rgba(0, 0, 0, 1)">{
</span><span style="color: rgba(0, 128, 128, 1)">3</span> <span style="color: rgba(0, 0, 255, 1)">int</span> newSize = <span style="color: rgba(128, 0, 128, 1)">2</span> *<span style="color: rgba(0, 0, 0, 1)"> oldSize;
</span><span style="color: rgba(0, 128, 128, 1)">4</span> <span style="color: rgba(0, 0, 255, 1)">if</span> ((<span style="color: rgba(0, 0, 255, 1)">uint</span>)newSize > MaxPrimeArrayLength && MaxPrimeArrayLength > oldSize) <span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> MaxPrimeArrayLength;
</span><span style="color: rgba(0, 128, 128, 1)">5</span> <span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> GetPrime(newSize);
</span><span style="color: rgba(0, 128, 128, 1)">6</span> } </pre>
</div>
<div><span style="font-family: 宋体, "Songti SC""><span style="font-family: 宋体, "Songti SC""> 对于新的entries数组的扩容,首先按照原始数组大小*2,那么对于能够扩容的最大数值为MaxPrimeArrayLength=0x7FFFFFC3,对应32字节的最大值。计算新的数组大小时,会基于原始数组2倍的情况下,再取对应的最少素数相乘,即:realSize=2*oldSize*y(素数表中的最少素数)。</span></span></div>
<div><span style="font-family: 宋体, "Songti SC""><span style="font-family: 宋体, "Songti SC""> 【备注:其实在整个C#的扩容逻辑中,绝大数大都是按照2倍进行扩容(按照2倍扩容的方式存在一定的弊端,</span></span><span style="font-family: 宋体, "Songti SC"">假设第n次扩容分配了2^n的空间(省略常</span><span style="font-family: 宋体, "Songti SC"">数C),那么之前释放掉的空间总和为:</span><span style="font-family: 宋体, "Songti SC"">1 + 2 + 2^2 + ... + 2^(n-1) = 2^n - 1 </span><span style="font-family: 宋体, "Songti SC"">正好放不下2^n的空间。这样导致的结果就是需要操作系统不断分配新的内存页,并且数组的</span><span style="font-family: 宋体, "Songti SC"">首地址也在不断变大,造成缓存缺失。】</span></div>
<div><span style="font-family: 宋体, "Songti SC""> Array.Copy(_entries, entries, count)扩容后的新数组会将对旧数组进行Copy()操作,在C#中每次对数组进行扩容时,都是将就数组的元素全部拷贝到新的数组中,这个过程是比较耗时和浪费资源,如果在实际的开发过程中提前计算好数组的容量,可以极大限度的提升性能,降低GC的活动频率。</span></div>
<div><span style="font-family: 宋体, "Songti SC""> 其中对于初始化为设置Dictionary的capacity时,第一次插入元素时,C#会对两个数组进行初始化,其中size=3,即维护的素数表中的最小值,后续超过该数组大小后,会按照以上的扩容逻辑进行扩容。</span></div>
<div><span style="font-family: 宋体, "Songti SC"; font-size: 14pt"><strong>四、Dictionary<TKey, TValue>的FindValue的实现方式</strong></span></div>
<div><span style="font-family: 宋体, "Songti SC""> 介绍完毕Dictionary<TKey, TValue>的元素插入后,我们接下来看一下Dictionary<TKey, TValue>的查询逻辑,在Dictionary<TKey, TValue>中实现查询逻辑的核心方法是FindValue(),首先我们来看一下其实现的源码。</span></div>
<div>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 128, 128, 1)"> 1</span> <span style="color: rgba(0, 0, 255, 1)">internal</span> <span style="color: rgba(0, 0, 255, 1)">ref</span><span style="color: rgba(0, 0, 0, 1)"> TValue FindValue(TKey key)
</span><span style="color: rgba(0, 128, 128, 1)"> 2</span> <span style="color: rgba(0, 0, 0, 1)">{
</span><span style="color: rgba(0, 128, 128, 1)"> 3</span> <span style="color: rgba(0, 0, 255, 1)">ref</span> Entry entry = <span style="color: rgba(0, 0, 255, 1)">ref</span> Unsafe.NullRef<Entry><span style="color: rgba(0, 0, 0, 1)">();
</span><span style="color: rgba(0, 128, 128, 1)"> 4</span> <span style="color: rgba(0, 0, 255, 1)">if</span> (_buckets != <span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">)
</span><span style="color: rgba(0, 128, 128, 1)"> 5</span> <span style="color: rgba(0, 0, 0, 1)">{
</span><span style="color: rgba(0, 128, 128, 1)"> 6</span> <span style="color: rgba(0, 0, 255, 1)">uint</span> hashCode = (<span style="color: rgba(0, 0, 255, 1)">uint</span><span style="color: rgba(0, 0, 0, 1)">)key.GetHashCode();
</span><span style="color: rgba(0, 128, 128, 1)"> 7</span> <span style="color: rgba(0, 0, 255, 1)">int</span> i =<span style="color: rgba(0, 0, 0, 1)"> GetBucket(hashCode);
</span><span style="color: rgba(0, 128, 128, 1)"> 8</span> Entry[]? entries =<span style="color: rgba(0, 0, 0, 1)"> _entries;
</span><span style="color: rgba(0, 128, 128, 1)"> 9</span> <span style="color: rgba(0, 0, 255, 1)">uint</span> collisionCount = <span style="color: rgba(128, 0, 128, 1)">0</span><span style="color: rgba(0, 0, 0, 1)">;
</span><span style="color: rgba(0, 128, 128, 1)">10</span> i--<span style="color: rgba(0, 0, 0, 1)">;
</span><span style="color: rgba(0, 128, 128, 1)">11</span> <span style="color: rgba(0, 0, 255, 1)">do</span>
<span style="color: rgba(0, 128, 128, 1)">12</span> <span style="color: rgba(0, 0, 0, 1)"> {
</span><span style="color: rgba(0, 128, 128, 1)">13</span> <span style="color: rgba(0, 0, 255, 1)">if</span> ((<span style="color: rgba(0, 0, 255, 1)">uint</span>)i >= (<span style="color: rgba(0, 0, 255, 1)">uint</span><span style="color: rgba(0, 0, 0, 1)">)entries.Length)
</span><span style="color: rgba(0, 128, 128, 1)">14</span> <span style="color: rgba(0, 0, 0, 1)"> {
</span><span style="color: rgba(0, 128, 128, 1)">15</span> <span style="color: rgba(0, 0, 255, 1)">goto</span><span style="color: rgba(0, 0, 0, 1)"> ReturnNotFound;
</span><span style="color: rgba(0, 128, 128, 1)">16</span> <span style="color: rgba(0, 0, 0, 1)"> }
</span><span style="color: rgba(0, 128, 128, 1)">17</span> entry = <span style="color: rgba(0, 0, 255, 1)">ref</span><span style="color: rgba(0, 0, 0, 1)"> entries;
</span><span style="color: rgba(0, 128, 128, 1)">18</span> <span style="color: rgba(0, 0, 255, 1)">if</span> (entry.hashCode == hashCode && EqualityComparer<TKey><span style="color: rgba(0, 0, 0, 1)">.Default.Equals(entry.key, key))
</span><span style="color: rgba(0, 128, 128, 1)">19</span> <span style="color: rgba(0, 0, 0, 1)"> {
</span><span style="color: rgba(0, 128, 128, 1)">20</span> <span style="color: rgba(0, 0, 255, 1)">goto</span><span style="color: rgba(0, 0, 0, 1)"> ReturnFound;
</span><span style="color: rgba(0, 128, 128, 1)">21</span> <span style="color: rgba(0, 0, 0, 1)"> }
</span><span style="color: rgba(0, 128, 128, 1)">22</span> i =<span style="color: rgba(0, 0, 0, 1)"> entry.next;
</span><span style="color: rgba(0, 128, 128, 1)">23</span> collisionCount++<span style="color: rgba(0, 0, 0, 1)">;
</span><span style="color: rgba(0, 128, 128, 1)">24</span> } <span style="color: rgba(0, 0, 255, 1)">while</span> (collisionCount <= (<span style="color: rgba(0, 0, 255, 1)">uint</span><span style="color: rgba(0, 0, 0, 1)">)entries.Length);
</span><span style="color: rgba(0, 128, 128, 1)">25</span> <span style="color: rgba(0, 0, 255, 1)">goto</span><span style="color: rgba(0, 0, 0, 1)"> ConcurrentOperation;
</span><span style="color: rgba(0, 128, 128, 1)">26</span> <span style="color: rgba(0, 0, 0, 1)"> }
</span><span style="color: rgba(0, 128, 128, 1)">27</span> <span style="color: rgba(0, 0, 255, 1)">goto</span><span style="color: rgba(0, 0, 0, 1)"> ReturnNotFound;
</span><span style="color: rgba(0, 128, 128, 1)">28</span> <span style="color: rgba(0, 0, 0, 1)"> ConcurrentOperation:
</span><span style="color: rgba(0, 128, 128, 1)">29</span> <span style="color: rgba(0, 0, 0, 1)"> ThrowHelper.ThrowInvalidOperationException_ConcurrentOperationsNotSupported();
</span><span style="color: rgba(0, 128, 128, 1)">30</span> <span style="color: rgba(0, 0, 0, 1)"> ReturnFound:
</span><span style="color: rgba(0, 128, 128, 1)">31</span> <span style="color: rgba(0, 0, 255, 1)">ref</span> TValue value = <span style="color: rgba(0, 0, 255, 1)">ref</span><span style="color: rgba(0, 0, 0, 1)"> entry.value;
</span><span style="color: rgba(0, 128, 128, 1)">32</span> <span style="color: rgba(0, 0, 0, 1)"> Return:
</span><span style="color: rgba(0, 128, 128, 1)">33</span> <span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(0, 0, 255, 1)">ref</span><span style="color: rgba(0, 0, 0, 1)"> value;
</span><span style="color: rgba(0, 128, 128, 1)">34</span> <span style="color: rgba(0, 0, 0, 1)"> ReturnNotFound:
</span><span style="color: rgba(0, 128, 128, 1)">35</span> value = <span style="color: rgba(0, 0, 255, 1)">ref</span> Unsafe.NullRef<TValue><span style="color: rgba(0, 0, 0, 1)">();
</span><span style="color: rgba(0, 128, 128, 1)">36</span> <span style="color: rgba(0, 0, 255, 1)">goto</span><span style="color: rgba(0, 0, 0, 1)"> Return;
</span><span style="color: rgba(0, 128, 128, 1)">37</span> }</pre>
</div>
<div><span style="font-family: 宋体, "Songti SC""> 以上的源码中,对于计算hashCode和计算哈希索引的桶的逻辑就不再赘述,重点关注entry.hashCode == hashCode &&EqualityComparer.Default.Equals(entry.key, key)),在FindValue()中,对已经缓存的Entry[]? entries进行循环遍历,然后依次进行比较,其中比较的逻辑包含两部分。在判断取值key时,不仅需要判断传入key值的hashCode与对应Entry[]? entries中的元素的hashCode值相等,还需要判断key是否相同,通过EqualityComparer.Default.Equals(entry.key, key)进行比较,关于比较器的逻辑将在下一章中进行介绍。</span></div>
<div><span style="font-family: 宋体, "Songti SC"; font-size: 14pt"><strong>五、学在最后的思考和感悟</strong></span></div>
<div><span style="font-family: 宋体, "Songti SC""> 上面介绍了Dictionary<TKey, TValue>的初始化、元素插入、元素插入时的扩容、元素取值的部分逻辑,我们可以发现在Dictionary<TKey, TValue>中维护了nt[] buckets和Entry[]? _entries两个数组,其中用于存储数据的结构为Entry[]? _entries,这个类型为一个结构体,在C#中结构体占用的内存要小于一个对象的内存占用。无论多么复杂的存储结构,其内部会尽量将其简化为一个数组,然后通过数组的存储和读取特性进行优化,规避了数组在某方面的不足,发挥了其优势。</span></div>
<div><span style="font-family: 宋体, "Songti SC""> 以上的部分思考中,我们其实可以发现在实际的编码过程中,需要注意的几个事项:</span></div>
<div><span style="font-family: 宋体, "Songti SC""> (1)、创建存储结构时,需要思考其对应的存储场景和对象,尽量选择合适的结构进行处理,降低内存的占用情况。</span></div>
<div><span style="font-family: 宋体, "Songti SC""> (2)、对于存储结构,尽量可以提前指定容量,避免频繁的扩容,每次扩容都会伴随数组的复制。</span></div>
<div><span style="font-family: 宋体, "Songti SC""> (3)、C#的扩容机制都是按照扩容2倍,在hash存储结构中,还会按照维护的素数表进行个性化的计算优化。</span></div>
<div><span style="font-family: 宋体, "Songti SC""> (4)、解读源码时,可以先选择一个简单的场景,尽量剔除与需要验证场景无关的代码,集中核心逻辑进行分析,然后再逐步进行扩展思考。</span></div>
<div><span style="font-family: 宋体, "Songti SC""> 以上内容是对CoreFx中Dictionary<TKey, TValue>的存储和读取逻辑的简单介绍,如错漏的地方,还望指正。</span></div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div id="MySignature" role="contentinfo">
爱知求真,静心钻研,虚心学习,务实创新,细致平和。<br><br>
来源:https://www.cnblogs.com/pengze0902/p/17830689.html
頁:
[1]