* 若结构中存在关键字和K相等的记录,则必定存储在f(K)的位置上。由此,不需比较便可直接取得所查记录。这个对应关系f称为散列函数(Hash function),按这个思想建立的表为散列表。
* 对不同的关键字可能得到同一散列地址,即key1≠key2,而f(key1)=f(key2),这种现象称冲突。具有相同函数值的关键字对该散列函数来说称做同义词。
根据散列函数H(key)和处理冲突的方法将一组关键字映象到一个有限的连续的地址集(区间)上,并以关键字在地址集中的“象”, 作为这条记录在表中的存储位置,这种表便称为散列表,这一映象过程称为散列造表或散列,所得的存储位置称散列地址。这个现象也叫散列桶,在散列桶中,只能通过顺序的方式来查找,一般只需要查找三次就可以找到。科学家计算过,当负载因子(load factor)不超过75%,查找效率最高。
散列表(Hash table,也叫哈希表):是根据关键码值(Key value)而直接进行访问的数据结构。也就是说,它通过映射的位置(即把关键码值映射到表中一个位置)来访问记录,以加快查找的速度。这个映射函数叫做散列函数,存放记录的数组叫做散列表。
以下是别人写的文章,加深对散列表的理解:
哈希表是种数据结构,它可以提供快速的插入操作和查找操作。第一次接触哈希表时,它的优点多得让人难以置信。不论哈希表中有多少数据,插入和删除(有时包括侧除)只需要接近常量的时间即0(1)的时间级。实际上,这只需要几条机器指令。
对哈希表的使用者一一人来说,这是一瞬间的事。哈希表运算得非常快,在计算机程序中,如果需要在一秒种内查找上千条记录通常使用哈希表(例如拼写检查器)哈希表的速度明显比树快,树的操作通常需要O(N)的时间级。哈希表不仅速度快,编程实现也相对容易。
哈希表也有一些缺点它是基于数组的,数组创建后难于扩展某些哈希表被基本填满时,性能下降得非常严重,所以程序虽必须要清楚表中将要存储多少数据(或者准备好定期地把数据转移到更大的哈希表中,这是个费时的过程)。
而且,也没有一种简便的方法可以以任何一种顺序〔例如从小到大〕遍历表中数据项。如果需要这种能力,就只能选择其他数据结构。
然而如果不需要有序遍历数据,井且可以提前预测数据量的大小。那么哈希表在速度和易用性方面是无与伦比的。
哈希表算法-哈希表的概念及作用
一般的线性表,树中,记录在结构中的相对位置是随机的,即和记录的关键字之间不存在确定的关系,因此,在结构中查找记录时需进行一系列和关键字的比较。这一类查找方法建立在“比较“的基础上,查找的效率依赖于查找过程中所进行的比较次数。
理想的情况是能直接找到需要的记录,因此必须在记录的存储位置和它的关键字之间建立一个确定的对应关系f,使每个关键字和结构中一个唯一的存储位置相对应。
哈希表最常见的例子是以学生学号为关键字的成绩表,1号学生的记录位置在第一条,10号学生的记录位置在第10条...
如果我们以学生姓名为关键字,如何建立查找表,使得根据姓名可以直接找到相应记录呢?
用上述得到的数值作为对应记录在表中的位置,得到下表:
上面这张表即哈希表。
如果将来要查李秋梅的成绩,可以用上述方法求出该记录所在位置:
李秋梅:lqm 12+17+13=42 取表中第42条记录即可。
问题:如果两个同学分别叫 刘丽 刘兰 该如何处理这两条记录?
这个问题是哈希表不可避免的,即冲突现象:对不同的关键字可能得到同一哈希地址。
哈希表算法-哈希表的构造方法
1、直接定址法
例如:有一个从1到100岁的人口数字统计表,其中,年龄作为关键字,哈希函数取关键字自身。
但这种方法效率不高,时间复杂度是O(1),空间复杂度是O(n),n是关键字的个数
2、数字分析法
有学生的生日数据如下:
年.月.日
75.10.03
75.11.23
76.03.02
76.07.12
75.04.21
76.02.15
...
经分析,第一位,第二位,第三位重复的可能性大,取这三位造成冲突的机会增加,所以尽量不取前三位,取后三位比较好。
3、平方取中法
取关键字平方后的中间几位为哈希地址。
4、折叠法
将关键字分割成位数相同的几部分(最后一部分的位数可以不同),然后取这几部分的叠加和(舍去进位)作为哈希地址,这方法称为折叠法。
例如:每一种西文图书都有一个国际标准图书编号,它是一个10位的十进制数字,若要以它作关键字建立一个哈希表,当馆藏书种类不到10,000时,可采用此法构造一个四位数的哈希函数。如果一本书的编号为0-442-20586-4,则:
5、除留余数法
取关键字被某个不大于哈希表表长m的数p除后所得余数为哈希地址。
H(key)=key MOD p (p<=m)
6、随机数法
选择一个随机函数,取关键字的随机函数值为它的哈希地址,即
H(key)=random(key) ,其中random为随机函数。通常用于关键字长度不等时采用此法。
5、除留余数法
取关键字被某个不大于哈希表表长m的数p除后所得余数为哈希地址。
H(key)=key MOD p (p<=m)
6、随机数法
选择一个随机函数,取关键字的随机函数值为它的哈希地址,即
H(key)=random(key) ,其中random为随机函数。通常用于关键字长度不等时采用此法。
5、除留余数法
取关键字被某个不大于哈希表表长m的数p除后所得余数为哈希地址。
H(key)=key MOD p (p<=m)
6、随机数法
选择一个随机函数,取关键字的随机函数值为它的哈希地址,即
H(key)=random(key) ,其中random为随机函数。通常用于关键字长度不等时采用此法。
哈希表算法-处理冲突的方法
如果两个同学分别叫 刘丽 刘兰,当加入刘兰时,地址24发生了冲突,我们可以以某种规律使用其它的存储位置,如果选择的一个其它位置仍有冲突,则再选下一个,直到找到没有冲突的位置。选择其它位置的方法有:
1、开放定址法
Hi=(H(key)+di) MOD m i=1,2,...,k(k<=m-1)
其中m为表长,di为增量序列
如果di值可能为1,2,3,...m-1,称线性探测再散列。
如果di取值可能为1,-1,2,-2,4,-4,9,-9,16,-16,...k*k,-k*k(k<=m/2)
称二次探测再散列。
如果di取值可能为伪随机数列。称伪随机探测再散列。
例:在长度为11的哈希表中已填有关键字分别为17,60,29的记录,现有第四个记录,其关键字为38,由哈希函数得到地址为5,若用线性探测再散列,如下:
2、再哈希法
当发生冲突时,使用第二个、第三个、哈希函数计算地址,直到无冲突时。缺点:计算时间增加。
3、链地址法
将所有关键字为同义词的记录存储在同一线性链表中。
4、建立一个公共溢出区
假设哈希函数的值域为[0,m-1],则设向量HashTable[0..m-1]为基本表,另外设立存储空间向量OverTable[0..v]用以存储发生冲突的记录。
转载地址: http://www.cnblogs.com/jiewei915/archive/2010/08/09/1796042.html
C#中实现了哈希表数据结构的集合类有:
(1) System.Collections.Hashtable
(2) System.Collections.Generic.Dictionary
前者为一般类型的哈希表,后者是泛型版本的哈希表。Dictionary和Hashtable之间并非只是简单的泛型和非泛型的区别,两者使用了完全不同的哈希冲突解决办法。
Hashtable 和 Dictionary <K, V> 类型 的区别
1:单线程程序中推荐使用 Dictionary, 有泛型优势, 且读取速度较快, 容量利用更充分.
2:多线程程序中推荐使用 Hashtable, 默认的 Hashtable 允许单线程写入, 多线程读取, 对 Hashtable 进一步调用 Synchronized() 方法可以获得完全线程安全的类型. 而 Dictionary 非线程安全, 必须人为使用 lock 语句进行保护, 效率大减.
3:Dictionary 有按插入顺序排列数据的特性 (注: 但当调用 Remove() 删除过节点后顺序被打乱), 因此在需要体现顺序的情境中使用 Dictionary 能获得一定方便.
4:对于值类型,特定类型(不包括 Object)的 Dictionary<(Of <(TKey, TValue>)>) 的性能优于 Hashtable,这是因为 Hashtable 的元素属于 Object 类型,所以在存储或检索值类型时通常发生装箱和取消装箱操作。
哈希冲突解决方法
哈希函数的目标是尽量减少冲突,但实际应用中冲突是无法避免的,所以在冲突发生时,必须有相应的解决方案。而发生冲突的可能性又跟以下两个因素有关:
(1) 装填因子α:所谓装填因子是指合希表中已存入的记录数n与哈希地址空间大小m的比值,即 α=n / m ,α越小,冲突发生的可能性就越小;α越大(最大可取1),冲突发生的可能性就越大。这很容易理解,因为α越小,哈希表中空闲单元的比例就越大,所以待插入记录同已插入的记录发生冲突的可能性就越小;反之,α越大,哈希表中空闲单元的比例就越小,所以待插入记录同已插入记录冲突的可能性就越大;另一方面,α越小,存储窨的利用率就越低;反之,存储窨的利用率就越高。为了既兼顾减少冲突的发生,又兼顾提高存储空间的利用率,通常把α控制在0.6~0.9的范围之内,C#的HashTable类把α的最大值定为0.72。
(2) 与所采用的哈希函数有关。若哈希函数选择得当,就可使哈希地址尽可能均匀地分布在哈希地址空间上,从而减少冲突的发生;否则,就可能使哈希地址集中于某些区域,从而加大冲突发生的可能性。
冲突解决技术可分为两大类:开散列法(又称为链地址法)和闭散列法(又称为开放地址法)。哈希表是用数组实现的一片连续的地址空间,两种冲突解决技术的区别在于发生冲突的元素是存储在这片数组的空间之外还是空间之内:
(1) 开散列法发生冲突的元素存储于数组空间之外。可以把“开”字理解为需要另外“开辟”空间存储发生冲突的元素。
(2) 闭散列法发生冲突的元素存储于数组空间之内。可以把“闭”字理解为所有元素,不管是否有冲突,都“关闭”于数组之中。闭散列法又称开放地址法,意指数组空间对所有元素,不管是否冲突都是开放的。
闭散列法(开放地址法)
闭散列法是把所有的元素存储在哈希表数组中。当发生冲突时,在冲突位置的附近寻找可存放记录的空单元。寻找“下一个”空位的过程称为探测。上述方法可用如下公式表示:
hi=(h(key)+di)%m i=1,2,…,k (k≤m-1)
其中h(key)为哈希函数;m为哈希表长;di为增量的序列。根据di取值的不同,可以分成几种探测方法,下面只介绍Hashtable所使用到的双重散列法。
双重散列法
双重散列法又称二度哈希,是闭散列法中较好的一种方法,它是以关键字的另一个散列函数值作为增量。设两个哈希函数为:h1和h2,则得到的探测序列为:
(h1(key)+h2(key))%m,(h1(key)+2h2(key))%m,(h1(key)+3h2(key))%m,…
其中,m为哈希表长。由此可知,双重散列法探测下一个开放地址的公式为:
(h1(key) + i * h2(key)) % m (1≤i≤m-1)
定义h2的方法较多,但无采用什么方法都必须使h2(key)的值和m互素(又称互质,表示两数的最大公约数为1,或者说是两数没有共同的因子,1除外)才能使发生冲突的同义词地址均匀地分布在整个哈希表中,否则可能造成同义词地址的循环计算。若m为素数,则h2取1至m-1之间的任何数均与m互素,因此可以简单地将h2定义为:
h2(key) = key % (m - 2) + 1
剖析System.Collections.Hashtable
万物之母object类中定义了一个GetHashCode()方法,这个方法默认的实现是返回一个唯一的整数值以保证在object的生命期中不被修改。既然每种类型都是直接或间接从object派生的,因此所有对象都可以访问该方法。自然,字符串或其他类型都能以唯一的数字值来表示。也就是说,GetHashCode()方法使得所有对象的哈希函数构造方法都趋于统一。当然,由于GetHashCode()方法是一个虚方法,你也可以通过重写这个方法来构造自己的哈希函数。
Hashtable的实现原理
Hashtable使用了闭散列法来解决冲突,它通过一个结构体bucket来表示哈希表中的单个元素,这个结构体中有三个成员:
(1) key :表示键,即哈希表中的关键字。
(2) val :表示值,即跟关键字所对应值。
(3) hash_coll :它是一个int类型,用于表示键所对应的哈希码。
int类型占据32个位的存储空间,它的最高位是符号位,为“0”时,表示这是一个正整数;为“1”时表示负整数。hash_coll使用最高位表示当前位置是否发生冲突,为“0”时,也就是为正数时,表示未发生冲突;为“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
【注意】:bucket结构体的hash_coll字段所存储的是h(key, i)的值而不是哈希地址。
哈希表的所有元素存放于一个名称为buckets(又称为数据桶) 的bucket数组之中,下面演示一个哈希表的数据的插入和删除过程,其中数据元素使用(键,值,哈希码)来表示。注意,本例假设Hashtable的长度为11,即hashsize = 11,这里只显示其中的前5个元素。
(1) 插入元素(k1,v1,1)和(k2,v2,2)。
由于插入的两个元素不存在冲突,所以直接使用h1(key) % hashsize的值做为其哈希码而忽略了h2(key)。其效果如图8.6所示。
(2) 插入元素(k3,v3,12)
新插入的元素的哈希码为12,由于哈希表长为11,12 % 11 = 1,所以新元素应该插入到索引1处,但由于索引1处已经被k1占据,所以需要使用h2(key)重新计算哈希码。
h2(key) = 1 + (((h1(key) >> 5) + 1) % (hashsize - 1))
h2(key) = 1 + ((12 >> 5) + 1) % (11 - 1)) = 2
新的哈希地址为 h1(key) + i * h2(key) = 1 + 1 * 2 = 3,所以k3插入到索引3处。而由于索引1处存在冲突,所以需要置其最高位为“1”。
(10000000000000000000000000000001)2 = (-2147483647)10
最终效果如图8.7所示。
(3) 插入元素(k4,v4,14)
k4的哈希码为14,14 % 11 = 3,而索引3处已被k3占据,所以使用二度哈希重新计算地址,得到新地址为14。索引3处存在冲突,所以需要置高位为“1”。
(12)10 = (00000000000000000000000000001100)2 高位置“1”后
(10000000000000000000000000001100)2 = (-2147483636)10
最终效果如图8.8所示。
(4) 删除元素k1和k2
Hashtable在删除一个存在冲突的元素时(hash_coll为负数),会把这个元素的key指向数组buckets,同时将该元素的hash_coll的低31位全部置“0”而保留最高位,由于原hash_coll为负数,所以最高位为“1”。
(10000000000000000000000000000000)2 = (-2147483648)10
单凭判断hash_coll的值是否为-2147483648无法判断某个索引处是否为空,因为当索引0处存在冲突时,它的hash_coll的值同样也为-2147483648,这也是为什么要把key指向buckets的原因。这里把key指向buckets并且hash_coll值为-2147483648的空位称为“有冲突空位”。如图8.8所示,当k1被删除后,索引1处的空位就是有冲突空位。
Hashtable在删除一个不存在冲突的元素时(hash_coll为正数),会把键和值都设为null,hash_coll的值设为0。这种没有冲突的空位称为“无冲突空位”,如图8.9所示,k2被删除后索引2处就属于无冲突空位,当一个Hashtable被初始化后,buckets数组中的所有位置都是无冲突空位。
哈希表通过关键字查找元素时,首先计算出键的哈希地址,然后通过这个哈希地址直接访问数组的相应位置并对比两个键值,如果相同,则查找成功并返回;如果不同,则根据hash_coll的值来决定下一步操作。当hash_coll为0或正数时,表明没有冲突,此时查找失败;如果hash_coll为负数时,表明存在冲突,此时需通过二度哈希继续计算哈希地址进行查找,如此反复直到找到相应的键值表明查找成功,如果在查找过程中遇到hash_coll为正数或计算二度哈希的次数等于哈希表长度则查找失败。由此可知,将hash_coll的高位设为冲突位主要是为了提高查找速度,避免无意义地多次计算二度哈希的情况。
Hashtable的代码实现
哈希表的实现较为复杂,为了简化代码,本例忽略了部分出错判断,在测试时请不要设key值为空
using System; 2 public class Hashtable 3 { 4 private struct bucket 5 { 6 public Object key; //键 7 public Object val; //值 8 public int hash_coll; //哈希码 9 } 10 private bucket[] buckets; //存储哈希表数据的数组(数据桶) 11 private int count; //元素个数 12 private int loadsize; //当前允许存储的元素个数 13 private float loadFactor; //填充因子 14 //默认构造方法 15 public Hashtable() : this(0, 1.0f) { } 16 //指定容量的构造方法 17 public Hashtable(int capacity, float loadFactor) 18 { 19 if (!(loadFactor >= 0.1f && loadFactor <= 1.0f)) 20 throw new ArgumentOutOfRangeException( 21 "填充因子必须在0.1~1之间"); 22 this.loadFactor = loadFactor > 0.72f ? 0.72f : loadFactor; 23 //根据容量计算表长 24 double rawsize = capacity / this.loadFactor; 25 int hashsize = (rawsize > 11) ? //表长为大于11的素数 26 HashHelpers.GetPrime((int)rawsize) : 11; 27 buckets = new bucket[hashsize]; //初始化容器 28 loadsize = (int)(this.loadFactor * hashsize); 29 } 30 public virtual void Add(Object key, Object value) //添加 31 { 32 Insert(key, value, true); 33 } 34 //哈希码初始化 35 private uint InitHash(Object key,int hashsize, 36 out uint seed,out uint incr) 37 { 38 uint hashcode = (uint)GetHash(key) & 0x7FFFFFFF; //取绝对值 39 seed = (uint)hashcode; //h1 40 incr = (uint)(1 + (((seed >> 5)+1) % ((uint)hashsize-1)));//h2 41 return hashcode; //返回哈希码 42 } 43 public virtual Object this[Object key] //索引器 44 { 45 get 46 { 47 uint seed; //h1 48 uint incr; //h2 49 uint hashcode = InitHash(key, buckets.Length, 50 out seed, out incr); 51 int ntry = 0; //用于表示h(key,i)中的i值 52 bucket b; 53 int bn = (int)(seed % (uint)buckets.Length); //h(key,0) 54 do 55 { 56 b = buckets[bn]; 57 if (b.key == null) //b为无冲突空位时 58 { //找不到相应的键,返回空 59 return null; 60 } 61 if (((b.hash_coll & 0x7FFFFFFF) == hashcode) && 62 KeyEquals(b.key, key)) 63 { //查找成功 64 return b.val; 65 } 66 bn = (int)(((long)bn + incr) % 67 (uint)buckets.Length); //h(key+i) 68 } while (b.hash_coll < 0 && ++ntry < buckets.Length); 69 return null; 70 } 71 set 72 { 73 Insert(key, value, false); 74 } 75 } 76 private void expand() //扩容 77 { //使新的容量为旧容量的近似两倍 78 int rawsize = HashHelpers.GetPrime(buckets.Length * 2); 79 rehash(rawsize); 80 } 81 private void rehash(int newsize) //按新容量扩容 82 { 83 bucket[] newBuckets = new bucket[newsize]; 84 for (int nb = 0; nb < buckets.Length; nb++) 85 { 86 bucket oldb = buckets[nb]; 87 if ((oldb.key != null) && (oldb.key != buckets)) 88 { 89 putEntry(newBuckets, oldb.key, oldb.val, 90 oldb.hash_coll & 0x7FFFFFFF); 91 } 92 } 93 buckets = newBuckets; 94 loadsize = (int)(loadFactor * newsize); 95 return; 96 } 97 //在新数组内添加旧数组的一个元素 98 private void putEntry(bucket[] newBuckets, Object key, 99 Object nvalue, int hashcode) 100 { 101 uint seed = (uint)hashcode; //h1 102 uint incr = (uint)(1 + (((seed >> 5) + 1) % 103 ((uint)newBuckets.Length - 1))); //h2 104 int bn = (int)(seed % (uint)newBuckets.Length);//哈希地址 105 do 106 { //当前位置为有冲突空位或无冲突空位时都可添加新元素 107 if ((newBuckets[bn].key == null) || 108 (newBuckets[bn].key == buckets)) 109 { //赋值 110 newBuckets[bn].val = nvalue; 111 newBuckets[bn].key = key; 112 newBuckets[bn].hash_coll |= hashcode; 113 return; 114 } 115 //当前位置已存在其他元素时 116 if (newBuckets[bn].hash_coll >= 0) 117 { //置hash_coll的高位为1 118 newBuckets[bn].hash_coll |= 119 unchecked((int)0x80000000); 120 } 121 //二度哈希h1(key)+h2(key) 122 bn = (int)(((long)bn + incr) % (uint)newBuckets.Length); 123 } while (true); 124 } 125 protected virtual int GetHash(Object key) 126 { //获取哈希码 127 return key.GetHashCode(); 128 } 129 protected virtual bool KeyEquals(Object item, Object key) 130 { //用于判断两key是否相等 131 return item == null ? false : item.Equals(key); 132 } 133 //当add为true时用作添加元素,当add为false时用作修改元素值 134 private void Insert(Object key, Object nvalue, bool add) 135 { //如果超过允许存放元素个数的上限则扩容 136 if (count >= loadsize) 137 { 138 expand(); 139 } 140 uint seed; //h1 141 uint incr; //h2 142 uint hashcode = InitHash(key, buckets.Length,out seed, out incr); 143 int ntry = 0; //用于表示h(key,i)中的i值 144 int emptySlotNumber = -1; //用于记录空位 145 int bn = (int)(seed % (uint)buckets.Length); //索引号 146 do 147 { //如果是有冲突空位,需继续向后查找以确定是否存在相同的键 148 if (emptySlotNumber == -1 && (buckets[bn].key == buckets) && 149 (buckets[bn].hash_coll < 0)) 150 { 151 emptySlotNumber = bn; 152 } 153 if (buckets[bn].key == null) //确定没有重复键才添加 154 { 155 if (emptySlotNumber != -1) //使用之前的空位 156 bn = emptySlotNumber; 157 buckets[bn].val = nvalue; 158 buckets[bn].key = key; 159 buckets[bn].hash_coll |= (int)hashcode; 160 count++; 161 return; 162 } 163 //找到重复键 164 if (((buckets[bn].hash_coll & 0x7FFFFFFF)==hashcode) && 165 KeyEquals(buckets[bn].key, key)) 166 { //如果处于添加元素状态,则由于出现重复键而报错 167 if (add) 168 { 169 throw new ArgumentException("添加了重复的键值!"); 170 } 171 buckets[bn].val = nvalue; //修改批定键的元素 172 return; 173 } 174 //存在冲突则置hash_coll的最高位为1 175 if (emptySlotNumber == -1) 176 { 177 if (buckets[bn].hash_coll >= 0) 178 { 179 buckets[bn].hash_coll |= unchecked((int)0x80000000); 180 } 181 } 182 bn = (int)(((long)bn + incr) % (uint)buckets.Length);//二度哈希 183 } while (++ntry < buckets.Length); 184 throw new InvalidOperationException("添加失败!"); 185 } 186 public virtual void Remove(Object key) //移除一个元素 187 { 188 uint seed; //h1 189 uint incr; //h2 190 uint hashcode = InitHash(key, buckets.Length,out seed, out incr); 191 int ntry = 0; //h(key,i)中的i 192 bucket b; 193 int bn = (int)(seed % (uint)buckets.Length); //哈希地址 194 do 195 { 196 b = buckets[bn]; 197 if (((b.hash_coll & 0x7FFFFFFF) == hashcode) && 198 KeyEquals(b.key, key)) //如果找到相应的键值 199 { //保留最高位,其余清0 200 buckets[bn].hash_coll &= unchecked((int)0x80000000); 201 if (buckets[bn].hash_coll != 0) //如果原来存在冲突 202 { //使key指向buckets 203 buckets[bn].key = buckets; 204 } 205 else //原来不存在冲突 206 { //置key为空 207 buckets[bn].key = null; 208 } 209 buckets[bn].val = null; //释放相应的“值”。 210 count--; 211 return; 212 } //二度哈希 213 bn = (int)(((long)bn + incr) % (uint)buckets.Length); 214 } while (b.hash_coll < 0 && ++ntry < buckets.Length); 215 } 216 public override string ToString() 217 { 218 string s = string.Empty; 219 for (int i = 0; i < buckets.Length; i++) 220 { 221 if (buckets[i].key != null && buckets[i].key != buckets) 222 { //不为空位时打印索引、键、值、hash_coll 223 s += string.Format("{0,-5}{1,-8}{2,-8}{3,-8}\r\n", 224 i.ToString(), buckets[i].key.ToString(), 225 buckets[i].val.ToString(), 226 buckets[i].hash_coll.ToString()); 227 } 228 else 229 { //是空位时则打印索引和hash_coll 230 s += string.Format("{0,-21}{1,-8}\r\n", i.ToString(), 231 buckets[i].hash_coll.ToString()); 232 } 233 } 234 return s; 235 } 236 public virtual int Count //属性 237 { //获取元素个数 238 get { return count; } 239 } 240 }
Hashtable和ArrayList的实现有似的地方,比如两者都是以数组为基础做进一步地抽象而来,两者都可以成倍地自动扩展容量。