标签:bar 相同 body 存储位置 png 相关 var image 个数
哈希表(Hash Table)是一种根据关键字直接访问内存存储位置的数据结构。通过哈希表,数据元素的存放位置和数据元素的关键字之间建立起某种对应关系,建立这种对应关系的函数称为哈希函数(如图)。
哈希表的构造方法是:假设要存储的数据元素个数为n,设置一个长度为m(m≥n)的连续存储单元,分别以每个数据元素的关键字为自变量,通过哈希函数,把映射为内存单元的某个地址,并将该数据元素存储在该内存单元中。
从数学的角度来看,哈希函数实际上是关键字到内存单元的映射,因此我们希望通过哈希函数通过尽量简单的运算使得通过哈希函数计算出的哈希地址尽量均匀地被映射到一系列的内存单元中。构造哈希函数有三个要点:第一,运算过程要尽量简单高效,以提高哈希表的插入和检索效率;第二,哈希函数应该具有较好的散列性,以降低哈希冲突的概率;第三,哈希函数应具有较大的压缩性,以节省内存。一般有以下几种常见的方法:
在构造哈希表时,存在这样的问题,对于两个不同的关键字,通过我们的哈希函数计算哈希地址时却得到了相同的哈希地址,我们将这种现象称为哈希冲突(如图):
哈希冲突主要与两个因素相关:第一,填装因子,所谓的填装因子是指哈希表中已存入的数据元素个数与哈希地址空间大小的比值,即α=n/m,α越小,冲突的可能性就越小,相反则冲突可能性越大;但是α越小,哈希表的存储空间利用率也就很低,α越大,存储空间的利用率也就越高,为了兼顾哈希冲突和存储空间利用率,通常将α控制在0.6-0.9之间,而.NET中的Hashtable则直接将α的最大值定义为0.72(注:虽然微软官方MSDN中声明Hashtable默认填装因子为1.0,事实上所有的填装因子都为0.72的倍数);第二,与所用的哈希函数有关,如果哈希函数选择得当,就可以使哈希地址尽可能的均匀分布在哈希地址空间上,从而减少冲突的产生,但一个良好的哈希函数的得来很大程度上取决于大量的实践,不过幸好前人已经总结实践了很多高效的哈希函数,可以参考园子里大牛Lucifer的文章:数据结构 : Hash Table [I]。
哈希冲突通常是很难避免的,解决哈希冲突有很多种方法,通常分为两大类:
.NET中实现了哈希表的类分别是Hashtable和Dictionary<TKey, TValue>,Hashtable由包含集合元素的存储桶组成,存储桶是Hashtable中各元素的虚拟子组,与大多数集合中进行的搜索相比,存储桶可使搜索更为便捷。Dictionary则是泛型版本的哈希表,与Hashtable的功能相同,对于值类型,特定类型(不包括Object)的性能优先于Hashtable,这是因为Hashtable的元素属于Object类型,所以在存储或者检索类型时通常发生装箱和拆箱操作;除此之外,虽然微软宣称Hashtable是线程安全的,可以允许多个读线程或一个写线程访问,但是事实是它也并非线程安全,在.NET Framework 2.0新引入的Dictionary仍旧为解决这个问题,其中限于公共静态方法是线程安全的,因此可以说Dictionary是非线程安全的,而且对整个集合的枚举过程对二者而言都不是线程安全的,因为当出现枚举与写访问互相争用这种情况发生时,则必须在整个枚举过程中对整个集合加锁。如果我们在使用.NET Framework 4.0以上版本,我们可以使用线程安全的ConcurrentDictionary;另一个比较重要的区别在于,虽然它们都实现了哈希表,但是二者却使用了完全不同的哈希冲突解决方法,Hashtable解决冲突的方式是开放定址法,而Dictionary则采用了链表法。
Hashtable类中哈希函数的定义可以用如下递推公式来表示:
通过简单的数学推导就可以得出其通项式公式即Hashtable的哈希函数簇为:
因此我们就拥有了一系列的哈希函数:,当我们向哈希表中增加元素时,则依次尝试使用这些哈希函数,直到找到相应的空闲内存单元地址为止,这种方式称为二度哈希。
在Hashtable类中,包含元素的存储桶被定义在结构体bucket中:
1 private struct bucket 2 { 3 public object key; 4 public object val; 5 public int hash_coll; 6 }
其中前两个字段很容易理解,分别代表了哈希表中的关键字和值,对于第三个字段hash_coll,实际上保存了两种信息:关键字的哈希码和是否冲突,coll为collision(冲突)的缩写,该字段为32位整型类型,最高位为符号位,当最高位为0时,表示该数为正数,表示未发生冲突,为1时为负数,表示发生了冲突,剩下的其他位则用于保存哈希码。
下面我们来看一个简单的哈希表元素增删过程,使得我们对于哈希表的具体工作方式有一个更直观的了解,当我们未指定具体Hashtable容量大小时,来进行一组数据的插入操作,此时Hashtable类会自动初始化其容量为默认最小值3。
从.NET Framework 2.0开始,随着泛型的引入,类库提供一个新的命名空间System.Collection.Generic,并且在该命名空间下新增了Dictionary等泛型类。
Dictionary的哈希函数就相对简单,就是简单的除留余数法,对于冲突解决,Dictionary则采用了链表法,但是此时buckets数组已经退化为专门用于存储元素的位置(下标)的整型数组,包含元素的数据结构被定义为结构体Entry,通过一个Entry类型的数组entries专门用于存储元素,Entry的定义如下:
1 private struct Entry 2 { 3 public int hashCode; 4 public int next; 5 public TKey key; 6 public TValue value; 7 }
其中的next字段表示数组链表的下一个元素的下标,一个关于数据存储结构的简单图示如下:
我们用同样的方式来看看Dictionary插入和删除元素的简单过程:
标签:bar 相同 body 存储位置 png 相关 var image 个数
原文地址:http://www.cnblogs.com/stlong/p/6289112.html