您的位置:首页 > 产品设计 > UI/UE

.net集合类的研究-哈希表(一)--Hashtable,Dictionary<TKey,TValue>

2012-04-02 11:12 387 查看
今天来探究哈希表,.net内置的哈希表容器是Hashtable类,而Dictionary<TKey,TValue>是对应的泛型哈希表.

哈希表-Hashtable的实例化

一般我们实例化ArrayList或List<T>的时候,如果不指定容量,则其内部是赋值为一个静态的空数组。当有添加操作时,会实例化为一个长度为4的数组,如果容量满了以后,再添加,就会自动扩充为两倍的容量。

哈希表也有一个类似的情况,newHashtable()如果不指定长度,则会将内置bucket数组的长度默认指定为11。如果给定一个值如newHashtable(20),也并不会将bucket数组长度设置为20,而是循环内部一个素数数组,设置为刚好大于20的素数(也叫质数),此处是23。可知,哈希表内部存取数组长度总是一个素数。相关代码:

publicHashtable(intcapacity,floatloadFactor)
{
......//默认值为11
intnum2=(num>11.0)?HashHelpers.GetPrime((int)num):11;
this.buckets=newbucket[num2];
......
}

[ReliabilityContract(Consistency.WillNotCorruptState,Cer.Success)]
internalstaticintGetPrime(intmin)
{
......
//循环素数数组找出刚好大于min的素数
for(inti=0;i<primes.Length;i++)
{
intnum2=primes[i];
if(num2>=min)
{
returnnum2;
}
}
......

}

//初始化素数数组
staticHashHelpers()
{
primes=newint[]{
3,7,11,0x11,0x17,0x1d,0x25,0x2f,0x3b,0x47,0x59,0x6b,0x83,0xa3,0xc5,0xef,
0x125,0x161,0x1af,0x209,0x277,0x2f9,0x397,0x44f,0x52f,0x63d,0x78b,0x91d,0xaf1,0xd2b,0xfd1,0x12fd,
0x16cf,0x1b65,0x20e3,0x2777,0x2f6f,0x38ff,0x446f,0x521f,0x628d,0x7655,0x8e01,0xaa6b,0xcc89,0xf583,0x126a7,0x1619b,
0x1a857,0x1fd3b,0x26315,0x2dd67,0x3701b,0x42023,0x4f361,0x5f0ed,0x72125,0x88e31,0xa443b,0xc51eb,0xec8c1,0x11bdbf,0x154a3f,0x198c4f,
0x1ea867,0x24ca19,0x2c25c1,0x34fa1b,0x3f928f,0x4c4987,0x5b8b6f,0x6dda89
};
}


从源码可以得出一个结论:Hashtable总是创建一个比我们预想中要大一些的数组。

哈希表-Hashtable的存取方式

Hashtable内部包含了一个bucket类型的数组变量,bucket是一个结构,用来保存Key,Value和哈希值



从上面代码可以看出Hashtable跟ArrayList,List<T>一样,内部都是用Array数组进行存储数据。既然同样是数组,Hashtable和ArrayList存取方式有什么不同?

当Hashtable添加数据时,(例如:hash.Add(key))首先调用当前键值key的GetHashCode()方法,得到哈希值(该值为Int32),取该哈希值的绝对值跟内部数组(上面代码的buckets)的总容量进行余运算(就是'%'操作),得到一个小于总容量的值,把该值当做数组序号,存在对应的位置,通过Key获取数组序号的过程叫映射。

当然,两个不同的key值有可能的得到相同的序号,这叫哈希冲突,在此不做分析。

下面是部分源码:

privatevoidInsert(objectkey,objectnvalue,booladd)
{  ......
uintnum3=this.InitHash(key,this.buckets.Length,outnum,outnum2);
intnum4=0;
intindex=-1;  //num6就是通过余计算得到的数组序号。
intnum6=(int)(num%this.buckets.Length);
  ......
}

privateuintInitHash(objectkey,inthashsize,outuintseed,outuintincr)
{
  //获取key在CLR中的HashCode值,并取绝对值。
uintnum=(uint)(this.GetHash(key)&0x7fffffff);
seed=num;  //此处针对哈希冲突
incr=1+((uint)(((seed>>5)+1)%(hashsize-1)));
returnnum;
}


从Hashtable里取值的,也是通过映射Key值,找到数组中对应的存放索引位置,直接位置取出值,整个过程无需循环,时间复杂度为O(1),而ArrayList查找一个值则需要循环整个数组去逐个匹配,时间复杂度为O(n),由此可以看到哈希表在查询中的优势。
既然Hashtable本身也是数组存储,查询速度又快,那还要ArrayList做什么?存储数据的时候都用Hashtable不就行了?有句话说,XX打开了一扇门,就必然要关闭很多窗,现在分析一下Hashtable关闭的那些窗。

首先,哈希表不能有重复键值。

其次,哈希表会占用更多内存空间。

第一点显而易见,现在分析第二点。

哈希表-Hashtable添加时的内部扩充方式

privatestructbucket
{
publicobjectkey;
publicobjectval;
publicinthash_coll;
}

publicclassHashtable://相关接口
{
privateIEqualityComparer_keycomparer;
privateobject_syncRoot;
privatebucket[]buckets;
  //....

}


我们知道ArrayList和List<T>在添加数值时,如果容量不足,会新建一个二倍容量的数组,然后把原数组数据复制过去,达到容量扩充的目的,所以使用ArrayList和List<T>的时候,建议在实例化方法里指定容量。

那么Hashtable在添加操作时碰到内部数组容量不足会怎么做?

其实处理方法和ArrayList基本相似,也是二倍扩充,但略有不同。不同点在于:

首先,扩充的大小比二倍略大。上面在实例化Hashtable那一部分中提到,由于哈希算法本身的特点,数组长度总是一个素数,所以当需要扩充时,新的数组长度是刚好大于原数组二倍长度的素数,也就是说略大于二倍。

其次,数组在未存满的情况下就要扩充。Hashtable有一个加载因子为0.72f。实际能存储的数据量等于内部Array长度*0.72f。也就是说一个长度为11的数组,当存了7个数据以后,就不能再存储,需要要扩充容量。代码如下:

publicHashtable(intcapacity,floatloadFactor)
{
......
//初始状态loafFactor=1f
this.loadFactor=0.72f*loadFactor;
......

intnum2=(num>11.0)?HashHelpers.GetPrime((int)num):11;
this.buckets=newbucket[num2];//实际可加载量
this.loadsize=(int)(this.loadFactor*num2);

......
}

//Hashtable的Add方法内部调用了Insert方法
privatevoidInsert(objectkey,objectnvalue,booladd)
{
......

if(this.count>=this.loadsize)
{     //内部容量扩充的方法
this.expand();
}

......
}


也就是说在Hashtable中,假如初始化了100个位置,只有72个可用,其他28个不能用,但仍然要占着地方。

由此我们可以知道,如果保存的数据量相同,Hashtable要比ArrayList占用更多的内存空间。

哈希表-泛型Dictionary<TKey,TValue>和Hashtable比较

.net2.0之后出现了泛型,泛型避免了装箱拆箱造成的损失,在大多数情况下,使用泛型集合要比传统的集合有较高的性能。List<T>对应ArrayList,Dictionary<TKey,TValue>对应Hashtable。

Dictionary<TKey,TValue>除了泛型带来的优势以外,还在哈希值算法,哈希冲突检测上做了改进,也没有了100个位置28个不能用的空间浪费。所以当碰到哈希表使用时,优先考虑Dictionary<TKey,TValue>。

最后总结:哈希表在查询方面有明显优势,但会占用多一点的内存空间,当业务中的集合有大量的查找操作时,优先考虑哈希表,一旦决定使用哈希表,优先考虑泛型哈希表。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: