一、前言

  在之前有一次面试中,被问到你了解Dictionary的内部实现机制吗?当时只是简单的了问答了:Dictionary的内部结构是哈希表,从而可以快速进行查找。但是对于更深一步了解就不清楚了。所以面试回来之后,就打算好好研究下Dictionary的源码。所以也就有了这篇文章。

二、Dictionary源码剖析

  大家都知道,现在微软已经开源了.NET Framework的源码了,在线源码查看地址为:http://referencesource.microsoft.com/。通过查找可以找到.NET Framework类的源码。下面我们就一起来看下Dictionary源码。

  2.1 添加元素

  首先我们来查看下Dictionary.Add方法的实现。为了让大家更好地实现,下面抽取了Dictionary源码核心部分来进行分析,详细的分析代码如下所示:

// buckets是哈希表,用来存放Key的Hash值
// entries用来存放元素列表
// count是元素数量
private void Insert(TKey key, TValue value, bool add)
{
if (key == null)
{
throw new ArgumentNullException(key.ToString());
}
// 首先分配buckets和entries的空间
if (buckets == null) Initialize();
int hashCode = comparer.GetHashCode(key) & 0x7FFFFFFF; // 计算key值对应的哈希值(HashCode)
int targetBucket = hashCode % buckets.Length; // 对哈希值求余,获得需要对哈希表进行赋值的位置 #if FEATURE_RANDOMIZED_STRING_HASHING
int collisionCount = ;
#endif
// 处理冲突的处理逻辑
for (int i = buckets[targetBucket]; i >= ; i = entries[i].next)
{
if (entries[i].hashCode == hashCode && comparer.Equals(entries[i].key, key))
{
if (add)
{
throw new ArgumentNullException();
}
entries[i].value = value;
version++;
return;
} #if FEATURE_RANDOMIZED_STRING_HASHING
collisionCount++;
#endif
} int index; // index记录了元素在元素列表中的位置
if (freeCount > )
{
index = freeList;
freeList = entries[index].next;
freeCount--;
}
else
{
// 如果哈希表存放哈希值已满,则重新从primers数组中取出值来作为哈希表新的大小
if (count == entries.Length)
{
Resize();
targetBucket = hashCode % buckets.Length;
}
// 大小如果没满的逻辑
index = count;
count++;
} // 对元素列表进行赋值
entries[index].hashCode = hashCode;
entries[index].next = buckets[targetBucket];
entries[index].key = key;
entries[index].value = value;
// 对哈希表进行赋值
buckets[targetBucket] = index;
version++; #if FEATURE_RANDOMIZED_STRING_HASHING
if(collisionCount > HashHelpers.HashCollisionThreshold && HashHelpers.IsWellKnownEqualityComparer(comparer))
{
comparer = (IEqualityComparer<TKey>) HashHelpers.GetRandomizedEqualityComparer(comparer);
Resize(entries.Length, true);
}
#endif
}

  下面以一个实际的添加例子来具体分析下上面的添加元素代码,从而更好地理解Add方法的实现原理。

  Dictionary<int, string> myDictionary = new Dictionary<int, string>();
myDictionary.Add(, "Item 1");
myDictionary.Add(, "Item 2");
myDictionary.Add(, "Item 3");

  当添加第一个元素时,此时会分配哈希表buckets数组和entries数组的空间和初始大小为3,分配完成之后,会计算添加元素key值的哈希值,哈希值的计算由具体的哈希算法来实现的,假设1的哈希值为9的话,此时targetBucket = 9%buckets.Length(3)的值为0,index的值为0,则第一个元素存放在entries列表中的第一个位置,最后对哈希表进行赋值,此时赋值的位置为第0个位置,其值为index的值,所以为0,插入第一个元素后Dictionary的内部结构如下所示:

  后面添加元素的过程依次类推。其原理就是,buckets记录了元素的在元素列表的存储位置,也就相当于一个映射列表。在查找的时候,就可以通过key值的哈希值来与buckets数组长度求余来获得元素在元素列表中的索引,这样就可以快速定位元素的位置,从而获得元素的key对应的Value值。如上面的例子中,如果想找到key值为1对应的Value值时,此时计算1的哈希值为9,然后对buckets数组长度求余,此时获得的值正是0,这样就可以直接从entries[0].Value的方式来获取对应的Value的值,这也就是Dictionary能完成快速查找的实现原理。后面会通过Dictionary内部的查找源码来证实上面分析的过程。

  2.2 解决冲突

  在添加元素过程中,有一个很重要的问题,如果产生冲突怎么办?即如果我后面需要插入的一个元素(假设这个值为11吧)的key值的哈希值也为6,此时targetBucket的值也是为0,但元素列表中0的位置已经存放了元素了,这样就出现了冲突,那Dictionary是怎样处理这个冲突的呢?处理冲突的方法有很多种,Dictionary处理的方式是链接法。Dictionary会把发生冲突的元素链接之前元素的后面,通过next属性来指定冲突关系。此时Dictionary内部结构如下图所示:

三、Dictionary如何实现快速查找呢?

  针对于Dictionary实现快速查找的原因,在上面我们已经做了一个推断了,下面通过Dictionary内部的代码实现来验证下,具体的查找代码如下所示:

public TValue this[TKey key]
{
get
{
int i = FindEntry(key);
// 通过元素所在存在的位置直接获取其对应的Value
if (i >= ) return entries[i].value;
throw new KeyNotFoundException();
return default(TValue);
}
set
{
Insert(key, value, false);
}
} private int FindEntry(TKey key)
{
if (key == null)
{
throw new ArgumentNullException();
} if (buckets != null)
{
// 获得Key值对应的哈希值
int hashCode = comparer.GetHashCode(key) & 0x7FFFFFFF;
// 查找元素在元素列表中的位置,如果没有冲突的情况下,此时查找速度为O(1),存在冲突的情况下为O(N),N为存在冲突的次数
for (int i = buckets[hashCode % buckets.Length]; i >= ; i = entries[i].next)
{
if (entries[i].hashCode == hashCode && comparer.Equals(entries[i].key, key)) return i;
}
}
return -;
}

  通过代码可以看出,我们之前的分析是完成正确的。从中可以明白:Dictionary之所以能实现快速查找元素,其内部使用哈希表来存储元素对应的位置,然后我们可以通过哈希值快速地从哈希表中定位元素所在的位置索引,从而快速获取到key对应的Value值。

四、总结

   可以说,Dictionary的实现原理也是一种空间换时间的思路,多使用一个buckets的存储空间来存储元素的位置,从而来提升查找速度。

   接下来,我们新开一个领域驱动设计系列,还请大家多多拍砖。

   本文所有源码下载:DictonaryInDepth.zip

[C#进阶系列]专题二:你知道Dictionary查找速度为什么快吗?的更多相关文章

  1. 当我们说线程安全时,到底在说什么——Java进阶系列(二)

    原创文章,同步发自作者个人博客,转载请以超链接形式在文章开头处注明出处http://www.jasongj.com/java/thread_safe/ 多线程编程中的三个核心概念 原子性 这一点,跟数 ...

  2. [.NET领域驱动设计实战系列]专题二:结合领域驱动设计的面向服务架构来搭建网上书店

    一.前言 在前面专题一中,我已经介绍了我写这系列文章的初衷了.由于dax.net中的DDD框架和Byteart Retail案例并没有对其形成过程做一步步分析,而是把整个DDD的实现案例展现给我们,这 ...

  3. [你必须知道的NOSQL系列]专题二:Redis快速入门

    一.前言 在前一篇博文介绍了MongoDB基本操作,本来打算这篇博文继续介绍MongoDB的相关内容的,例如索引,主从备份等内容的,但是发现这些内容都可以通过官方文档都可以看到,并且都非常详细,所以这 ...

  4. 方法字段[C# 基础知识系列]专题二:委托的本质论

    首先声明,我是一个菜鸟.一下文章中出现技术误导情况盖不负责 引言: 上一个专题已和大家分享了我懂得的——C#中为什么须要委托,专题中简略介绍了下委托是什么以及委托简略的应用的,在这个专题中将对委托做进 ...

  5. [C# 基础知识系列]专题二:委托的本质论 (转载)

    引言: 上一个专题已经和大家分享了我理解的——C#中为什么需要委托,专题中简单介绍了下委托是什么以及委托简单的应用的,在这个专题中将对委托做进一步的介绍的,本专题主要对委本质和委托链进行讨论. 一.委 ...

  6. [C#进阶系列]专题一:深入解析深拷贝和浅拷贝

    一.前言 这个星期参加了一个面试,面试中问到深浅拷贝的区别,然后我就简单了讲述了它们的之间的区别,然后面试官又继续问,如何实现一个深拷贝呢?当时只回答回答了一种方式,就是使用反射,然后面试官提示还可以 ...

  7. [C#]网络编程系列专题二:HTTP协议详解

    转自:http://www.cnblogs.com/zhili/archive/2012/08/18/2634475.html 我们在用Asp.net技术开发Web应用程序后,当用户在浏览器输入一个网 ...

  8. javascript进阶系列专题:闭包(Closure)

    在javascript中,函数可看作是一种数据,可以赋值给变量,可以嵌套在另一个函数中. var fun = function(){ console.log("平底斜"); } f ...

  9. javascript进阶系列专题:作用域与作用域链

    字面意思,作用域是指变量和函数的作用范围,换言之,作用域决定了变量和函数的可见性和有效时间.javascript作用域是用函数来区分,与其他语言的大括号不同. for (var i=0; i<5 ...

随机推荐

  1. 关于启动 SecureCRT 遇到一个致命的错误且必须关闭

    --------------------------SecureCRT---------------------------SecureCRT 遇到一个致命的错误且必须关闭. 一个崩溃转储文件已创建于 ...

  2. JAVA第4次作业

    package fuzhi; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOExc ...

  3. IOS 数据库

    系统自带可以储存字段的字典: NSUserDefaults *user = [[NSUserDefaults alloc] init]; 存 : [user setObject:@"YES& ...

  4. 【环境配置】php5.5 + apache2.4 安装配置【转+修改】

    转自 http://my.oschina.net/lilinzero/blog/180509 [转+修改][环境配置]php5.5.10 + apache2.4 安装配置 根据自己的实践所得,稍微修改 ...

  5. NSURLSession的基本使用

    一.简单说明 在iOS9.0之后,以前使用的NSURLConnection过期,苹果推荐使用NSURLSession来替换NSURLConnection完成网路请求相关操作. NSURLSession ...

  6. css3之景深

    perspective属性:(目前仅仅支持-webkit-perspective属性,视点距离) 值:number perspective-origin属性:(视点位置) 值:number% numb ...

  7. OD使用教程9

    先运行程序打开about,发现这是一个未注册的软件需要注册后才能使用里面的工具,所以随便注册一下,跳出一个提示说注册的是非法的邮箱,所以就可以以此做为突破口来破解这个程序 将提示的语句作为关键字去找出 ...

  8. Kiwi iOS驱动测试开发

    Kiwi是iOS开发一个行为驱动开发库.目标是提供一个BDD库,精美的简单安装和使用. 安装 使用CocoaPods安装: pod 'Kiwi'如果使用Xcode5则使用: pod 'Kiwi/XCT ...

  9. 理解Java Integer的缓存策略

    转载自http://www.importnew.com/18884.html 本文将介绍 Java 中 Integer 缓存的相关知识.这是 Java 5 中引入的一个有助于节省内存.提高性能的特性. ...

  10. Swift基础语法(三)

    Array数组在swift中Array相当于oc中的NSMutableArray(变长数组) //1.定义数组 var numarr:Int[] = [,,,,]; var strarr:String ...