从内部剖析C# 集合之--Dictionary

Dictionary和hashtable用法有点相似,他们都是基于键值对的数据集合,但实际上他们内部的实现原理有很大的差异,

先简要概述一下他们主要的区别,稍后在分析Dictionary内部实现的大概原理。

区别:1,Dictionary支持泛型,而Hashtable不支持。

2,Dictionary没有装填因子(Load
Facto)概念,当容量不够时才扩容(扩容跟Hashtable一样,也是两倍于当前容量最小素数),Hashtable是“已装载元素”与”bucket数组长度“大于装载因子时扩容。

3,Dictionary内部的存储value的数组按先后插入的顺序排序,Hashtable不是。

4,当不发生碰撞时,查找Dictionary需要进行两次索引定位,Hashtable需一次,。

Dictionary采用除法散列法来计算存储地址,想详细了解的可以百度一下,简单来说就是其内部有两个数组:buckets数组和entries数组(entries是一个Entry结构数组),entries有一个next用来模拟链表,该字段存储一个int值,指向下一个存储地址(实际就是bukets数组的索引),当没有发生碰撞时,该字段为-1,发生了碰撞则存储一个int值,该值指向bukets数组.

下面跟上次一样,按正常使用Dictionary时,看内部是如何实现的。

一,实例化一个Dictionary, Dictionary<string,string>
dic=new Dictionary<string,string>();

    a,调用Dictionary默认无参构造函数。

b,初始化Dictionary内部数组容器:buckets
int[]和entries<T,V>[],分别分配长度3。(内部有一个素数数组:3,7,11,17....如图:);

  二,向dic添加一个值,dic.add("a","abc");

     a,将bucket数组和entries数组扩容3个长度。

b,计算"a"的哈希值,

c,然后与bucket数组长度(3)进行取模计算,假如结果为:2

d,因为a是第一次写入,则自动将a的值赋值到entriys[0]的key,同理将"abc"赋值给entriys[0].value,将上面b步骤的哈希值赋值给entriys[0].hashCode,

entriys[0].next 赋值为-1,hashCode赋值b步骤计算出来的哈希值。

e,在bucket[2]存储0。

三,通过key获取对应的value,  var v=dic["a"];

   a, 先计算"a"的哈希值,假如结果为2,

b,根据上一步骤结果,找到buckets数组索引为2上的值,假如该值为0.

c, 找到到entriys数组上索引为0的key,

1),如果该key值和输入的的“a”字符相同,则对应的value值就是需要查找的值。

2)
,如果该key值和输入的"a"字符不相同,说明发生了碰撞,这时获取对应的next值,根据next值定位buckets数组(buckets[next]),然后获取对应buckets上存储的值在定位到entriys数组上,......,一直到找到为止。

3),如果该key值和输入的"a"字符不相同并且对应的next值为-1,则说明Dictionary不包含字符“a”。

Dictionary里的其他方法就不说了,各位可以自己去看源码,下面来通过实验来对比Hashtable和Dictionary的添加和查找性能,

1,添加元素速度测评。

     循环5次,每次内部在循环10次取平均值,PS:代码中如有不公平的地方望各位指出,本人知错就改。

代码:

static void Main(string[] args)
{
for (int i = 0; i < 5; i++)
{
Console.WriteLine(string.Format("第{0}次执行:", i + 1));
Add();
Console.WriteLine("-------华丽的分分隔线---------");
}

Console.ReadKey();
}
public static void Add()
{
Hashtable ht = new Hashtable();
Stopwatch st = new Stopwatch();

long ticks1 = 0;
for (int j = 0; j < 10; j++)
{
st.Reset();
st.Start();
for (int i = 0; i < 1000000; i++)
{
ht.Add(i, i);
}
st.Stop();
ticks1 += st.ElapsedTicks;
ht.Clear();
}

Console.WriteLine(string.Format("Hashtable添加:{0}个元素,消耗:{1}", 1000000, ticks1 / 10));

Dictionary<int, int> dic = new Dictionary<int, int>();
ticks1 = 0;
for (int j = 0; j < 10; j++)
{
st.Reset();
st.Start();
for (int i = 0; i < 1000000; i++)
{
dic.Add(i, i);
}
st.Stop();
ticks1 += st.ElapsedTicks;
dic.Clear();
}

Console.WriteLine(string.Format("Dictionary添加:{0}个元素,消耗:{1}", 1000000, st.ElapsedTicks));
}

 结果:

通过运行结果来看,HashTable 速度明显慢于Dictionary,相差一个数量级。我个人分析原因可能为:

a,Hashtable不支持泛型,我向你添加的int类型会发生装箱操作,而Dictionary支持泛型。

b,Hashtable在扩容时会先new一个更大的数组,然后将原来的数据复制到新的数组里,还需对新数组里的key重新哈希计算(这可能是最性能影响最大的因素)。而Dictionary不会这样。

2,查找速度测评(两种情况:值类型和引用类型)

1 值类型

  static void Main(string[] args)
{

// GetByString();

GetByInt();

Console.ReadKey();
}

public static void GetByInt()
{
//Hashtable
Hashtable hs = new Hashtable();
Dictionary<int, int> dic = new Dictionary<int, int>();

for (int i = 0; i < 10000000; i++)
{
hs.Add(i, i);
dic.Add(i, i);
}
long ticks = 0;
Stopwatch st = new Stopwatch();
st.Reset();
for (int i = 0; i < 10; i++)
{
st.Start();
var result = hs[99999+i];
st.Stop();
ticks += st.ElapsedTicks;
st.Reset();
}
Console.WriteLine(string.Format("Hashtable查找10次,平均消耗:{0}", (float)ticks / 10));

//Dictionary
ticks = 0;
st.Reset();
for (int i = 0; i < 10; i++)
{
st.Start();
var result = dic[i];
st.Stop();
ticks += st.ElapsedTicks;
st.Reset();
}
Console.WriteLine(string.Format("Dictionary查找10次,平均消耗:{0}", (float)ticks / 10));
}

运行结果

2,引用类型

 1 static void Main(string[] args)
2 {
3 GetByString();
4
5 Console.ReadKey();
6 }
7
8 public static void GetByString()
9 {
10 //Hashtable
11 Hashtable hs = new Hashtable();
12 Dictionary<string, string> dic = new Dictionary<string, string>();
13
14 for (int i = 0; i < 1000000; i++)
15 {
16 hs.Add(i.ToString(), i.ToString());
17 dic.Add(i.ToString(), i.ToString());
18 }
19 long ticks = 0;
20 Stopwatch st = new Stopwatch();
21 st.Reset();
22 string key = "9999";
23 for (int i = 0; i < 10; i++)
24 {
25 st.Start();
26 var result = hs[key];
27 st.Stop();
28 ticks += st.ElapsedTicks;
29 st.Reset();
30 }
31 Console.WriteLine(string.Format("Hashtable查找10次,平均消耗:{0}", (float)ticks / 10));
32
33 //Dictionary
34 ticks = 0;
35 st.Reset();
36 for (int i = 0; i < 10; i++)
37 {
38 st.Start();
39 var result = dic[key];
40 st.Stop();
41 ticks += st.ElapsedTicks;
42 st.Reset();
43 }
44 Console.WriteLine(string.Format("Dictionary查找10次,平均消耗:{0}", (float)ticks / 10));
45 }

运行结果

根据上面实验结果可以得出:

a,值类型,Hashtable和Dictionary性能相差不大,Hashtable稍微快于Dictionary.

b,引用类型:Hashtable速度明显快于Dictionary。

 PS:以上是个人不成熟观点,如果错误请各位指出,谢谢,下篇介绍 SortedList
集合。

   
另:公司最近招聘.net和java程序员若干,如各位有找工作打算的,请发简历到[email protected]。

 
公司在上海闵行浦江智谷。

时间: 2024-11-08 16:44:14

从内部剖析C# 集合之--Dictionary的相关文章

从内部剖析C#集合之HashTable

计划写几篇文章专门介绍HashTable,Dictionary,HashSet,SortedList,List 等集合对象,从内部剖析原理,以便在实际应用中有针对性的选择使用. 这篇文章先介绍HashTable . 先例举几个问题:1,Hashtable为什么速度查询速度快,而添加速度相对慢,且其添加和查询速度之比相差一个数量等级? 2,装填因子( Load Factor)是什么,hashtable默认的装填因子是多少? 3,hashtable里的元素是顺序排序的吗? 4,hashtable内部

字典与集合(Dictionary与Collection)

Dictionary对象将替换Collection对象,并提供附加的语言从而使增加和删除记录的速度比以前提高三倍 虽然Visual Basic 6.0只有很少的新特点,但是具有某些功能强大的新的对象模型,其中之一就是Dictionary对象. Dictionary对象是无处不在的Visual Basic Collection对象的新版本.它的介绍存在于VBScript 2.0,并通过Visual Basic 6.0 对Scripting Runtime Library的支持涉入Visual Ba

C#泛型集合之Dictionary&lt;k, v&gt;使用技巧

1.要使用Dictionary集合,需要导入C#泛型命名空间 System.Collections.Generic(程序集:mscorlib) 2.描述 1).从一组键(Key)到一组值(Value)的映射,每一个添加项都是由一个值及其相关连的键组成 2).任何键都必须是唯一的 3).键不能为空引用null(VB中的Nothing),若值为引用类型,则可以为空值 4).Key和Value可以是任何类型(string,int,custom class 等) 3.创建及初始化 Dictionary<

支付宝系统架构内部剖析

本文转载于网上的文章 <最全最强解析:支付宝系统架构内部剖析(架构图)>. 最早的出处没有找到,原文作者也未知. 如果你知道请告诉我. 支付宝系统架构概况 典型处理默认 资金处理平台 财务会计 支付清算 核算中心 交易 柔性事务 消息系统 数据分布 数据缓存 支付宝技术产品线 支付宝的开源分布式消息中间件–Metamorphosis(MetaQ) 貌似原发起者离开了阿里,现在这个项目改名为RocketMQ Metamorphosis (MetaQ) 是一个高性能.高可用.可扩展的分布式消息中间

转 最全最强解析:支付宝钱包系统架构内部剖析(架构图)

原文: 最全最强解析:支付宝钱包系统架构内部剖析(架构图) 2015/06/05 支付宝系统架构概况 典型处理默认 资金处理平台 财务会计 支付清算 核算中心 交易 柔性事务 支付宝的开源分布式消息中间件–Metamorphosis(MetaQ) Metamorphosis (MetaQ) 是一个高性能.高可用.可扩展的分布式消息中间件,类似于LinkedIn的Kafka,具有消息存储顺序写.吞吐量大和支持本地和XA事务等特性,适用 于大吞吐量.顺序消息.广播和日志数据传输等场景,在淘宝和支付宝

[转] 支付宝系统架构内部剖析

本文转载于网上的文章 <最全最强解析:支付宝系统架构内部剖析(架构图)>. 最早的出处没有找到,原文作者也未知. 如果你知道请告诉我. 支付宝系统架构概况 典型处理默认 资金处理平台 财务会计 支付清算 核算中心 交易 柔性事务 消息系统 数据分布 数据缓存 支付宝技术产品线 支付宝的开源分布式消息中间件–Metamorphosis(MetaQ) 貌似原发起者离开了阿里,现在这个项目改名为RocketMQ Metamorphosis (MetaQ) 是一个高性能.高可用.可扩展的分布式消息中间

Memcache内部剖析

Memcache在web社区中是一个非常著名的系统,而且有一个好的原因:它速度快.稳定.轻量级,而且如果你在网站服务器安装了memcache后它似乎会自动将网站访问速度提升10倍.虽然这似乎有点不可思议,但是:定制一个好的缓存策略对网站或应用很有用.如果你只是想知道如果在你的网站中应用memcache,那很不幸,本文并不是教你如何使用memcache.我们将抽丝剥茧,看看是什么使memcache如此神奇? 尽管memcache本身并不是一个非常复杂的软件,但它却有许多很好的功能,这要花很长时间来

什么是Git?内部剖析

Git:是目前世界上最先进的分布式版本控制系统(没有之一). Git功能:进行版本控制管理 版本:一个文件每次保存后的样本. 如果你修改了一个文件,而你又不确定原来的版本以后可能用到,所以你就会保存原来的版本和新生成的版本,再次修改第二版本的时候,你又会生成一个版本,于是,关于一个文件,你有了每个版本的备份.如此一来,你管理肯定是极不方便的. 有些部分需要你的财务同事帮助填写,于是你把文件Copy到U盘里给她(也可能通过Email发送一份给她),然后,你继续修改Word文件.一天后,同事再把Wo

[转载] 最全最强解析:支付宝系统架构内部剖析(架构图)

支付宝系统架构概况 典型处理默认 资金处理平台 财务会计 支付清算 核算中心 交易 柔性事务 支付宝的开源分布式消息中间件–Metamorphosis(MetaQ) Metamorphosis (MetaQ) 是一个高性能.高可用.可扩展的分布式消息中间件,类似于LinkedIn的Kafka,具有消息存储顺序写.吞吐量大和支持本地和XA事务等特性,适用 于大吞吐量.顺序消息.广播和日志数据传输等场景,在淘宝和支付宝有着广泛的应用,现已开源. Metamorphosis是淘宝开源的一个Java消息