菜单 学习猿地 - LMONKEY

VIP

开通学习猿地VIP

尊享10项VIP特权 持续新增

知识通关挑战

打卡带练!告别无效练习

接私单赚外块

VIP优先接,累计金额超百万

学习猿地私房课免费学

大厂实战课仅对VIP开放

你的一对一导师

每月可免费咨询大牛30次

领取更多软件工程师实用特权

入驻
155
0

hashtable

原创
05/13 14:22
阅读数 90617

Hashtable

           internal const Int32 HashPrime = 101;//一个固定的素数

        private const Int32 InitialSize = 3;//哈希表的默认容量

 

        private struct bucket {

            public Object key;//键

            public Object val;//值

            public int hash_col;//哈希码,最高位是符号位,0:正整数,表示未发生冲突,1:负整数,表示发生冲突,存储的是h(key, i)的值而不是哈希地址

        }

 

        private bucket[] buckets;

        private int count;//元素总数

        private int occupancy;//冲突次数

 

        private  int loadsize;//相当于一个阈值,达到了这个数值,将对哈希表进行扩容

        private  float loadFactor;//哈希表中的元素占有数据桶空间的一个比率,这个值直接决定了哈希表在什么时候进行扩容。

 

        private volatile int version;

        private volatile bool isWriterInProgress;  

 

        private ICollection keys;

        private ICollection values;

在Hashtable中的两个哈希函数分别为:

1.  h_1(k) = k.GetHashCode():第一个哈希函数直接用默认的GetHashCode()方法;

2.  h_2(k) = (1 + ((h_1(k) * HashPrime) % (hashsize - 1))):HashPrime为私有成员101的素数,hashsize为哈希表长度。之所以会进行取模运算是为了保证结果值的范围在[0, hashsize - 1]。

构建哈希算法的函数

Hashtable解决冲突使用了双重散列法,但又跟前面所讲的双重散列法稍有不同。它探测地址的方法如下:

h(key, i) = h1(key) + i * h2(key)

其中哈希函数h1和h2的公式如下:

h1(key) = key.GetHashCode()

h2(key) = 1 + (((h1(key) >> 5) + 1) % (hashsize - 1))

由于使用了二度哈希,最终的h(key, i)的值有可能会大于hashsize,所以需要对h(key, i)进行模运算,最终计算的哈希地址为:

哈希地址 = h(key, i) % hashsize

 

哈希表通过关键字查找元素时,首先计算出键的哈希地址,然后通过这个哈希地址直接访问数组的相应位置并对比两个键值,如果相同,则查找成功并返回;如果不同,则根据hash_coll的值来决定下一步操作。当hash_coll为0或正数时,表明没有冲突,此时查找失败;如果hash_coll为负数时,表明存在冲突,此时需通过二度哈希继续计算哈希地址进行查找,如此反复直到找到相应的键值表明查找成功,如果在查找过程中遇到hash_coll为正数或计算二度哈希的次数等于哈希表长度则查找失败。由此可知,将hash_coll的高位设为冲突位主要是为了提高查找速度,避免无意义地多次计算二度哈希的情况。

 

一,new一个Hashtable,Hashtable ht=new Hashtable();

 

Hashtable有多个构造函数,常用的是无参构造函数:Hashtable ht=new Hashtable(),在new一个hashtable时,其内部做了如下工作:调用Hashtable(int capacity,float loadFactor),其中capacity为:0,loadFactor为:1,然后初始化bocket数组大小为3,装载因子为0.72(该值是微软权衡后给的值),如下图所示,该图截取Reflector

二,向Hashtable添加一个元素,ht.Add("a","123")

 

   1,判断当前Hashtable :ht的元素个数与bucket数组之比是否超过装载因子0.72,

 

       1)小于0.72:对a进行哈希取值,然后将得到的值与bucket数组长度进行取模计算,将取模的结果插入到bucket数组对应索引,将“123”赋值其value.

 

            因为哈希值可能会重复(不明白的百度一下),从而导致地址冲突,Hashtable 采用的是 "开放定址法" 处理冲突, 具体行为是把 HashOf(k) % Array.Length 改为 (HashOf(k) + d(k)) % Array.Length , 得出另外一个位置来存储关键字 "a" 所对应的数据, d 是一个增量函数. 如果仍然冲突, 则再次进行增量, 依此循环直到找到一个 Array 中的空位为止。

 

       2)  大于0.72:对bucket数组进行扩容,

a,   新建一个数组(该数组的大小为两倍于当前容量最小的素数,比如当前数组长度是3,那么新数组长度为7)。

b,    将原来数组元素拷贝到新的数组中,因为bocket数组长度变了,所以需要对所有key重新哈希计算(这是影响hashtable性能的重要因素)。

c,     进行上面a步骤。

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

 

    1) 计算"a"的哈希值。

 

    2)将计算的结果与bocket数组长度进行取模计算,因为哈希值可能会冲突,所以类似定位索引上的key可能与输入的key不相同,这时继续查找下一个位置。。。。。

 

3)取模计算结果即是存储在bocket数组上"123"的索引。

 

  1. Hashtable为什么查询速度快,而添加速度慢,且其添加和查询速度之比相差一个数量级?

Hashtable查询速度快是因为内部是基于数组索引定位的,稍微消耗性能的是取key’的哈希值,添加性能慢是因为添加元素可能会地址冲突,需要重新定位地址,扩容后数组拷贝,重新计算key。

  1. 装填因子(loadfactor)是什么,hashtable默认的装填因子时多少?

装填因子是hashtable中已存元素数/内部bucket数组长度,这个比值太大会造成冲突概率增大,太小会造成空间浪费,默认是0.72.

  1. Hashtable里的元素是顺序排序的吗?

不是顺序的

  1. Hashtable内部的数据桶默认长度是多少,其长度为什么只能是素数?

默认长度是3,因为素数有一个特点,只能被自己和1整除,如果不是素数,那么取模运算时可能会出现多个值。

发表评论

0/200
155 点赞
0 评论
收藏
为你推荐 换一批