字典(C#)之字典的工作原理

来源:互联网 发布:淘宝卖家id是什么 编辑:程序博客网 时间:2024/06/05 15:38

字典的使用非常方便,但这里有一个问题:Hashtable(和其它字典类)使用某种算法,根据键来确定每个对象的位置,实际上,该算法并不完全是由Hashtable提供的.它有两个部分,其中一部分的代码必须由key类来提供.如果使用一个Microsoft提供的类,并把这个类用作键(例如字符串),就不会有任何问题但如果自己编写的key,就必须自己编写这部分算法.

在计算机中,由key类执行的部分算法称为散列(因此字典也称为表),Hashtable在散列算法中有非常特殊的地位:它在对象的GetHashCode()方法中,继承了System.Object.只要字典类需要定位条目的位置,就会调用键对象的GegHashCode()方法.(感兴趣的同学可以查一下System.Object中的GetHashCode()).

其工作方式是GetHashCode()返回一个int,它应使用这个键的值来生成该int.Hashtable获取这个int,并对它进行其它的一些操作,其中涉及到一引起复杂的数学计算,最后返回一个索引,表示带有给定散列的条目在字典中的存储的位置.我们不详细介绍这部分算法,Microsoft 已经为这部分算法编写好了代码,所以不需要详细了解,但该算法用素数,而且这是散列表容量为什么应是一个素数的原因了.

为了使GetHashCode()重写方法正常工作,有一些相当严格的要求.这引起要求听起来相当抽象,难以理解,但不必太担心编写一个满足要求的key类也不是那么困难(如果有时间,再写出来后面的例子):

  • 该方法应比较快(因为在字典中旋转或获取条目要求比较快)
  • 该方法应比较一致,如果两个键表示相同的值,那么它们必须为散列提供相同的值.
  • 该方法给出的原因是有一个潜在的问题:如果在字典中,两个条目的散列有相同的索引,该怎么办?

如果发生这种情况,字典就必须寻找最近的可利用的自由单元来存储第二个条目,必须进行一定的搜索,以便以后猎取这个条目.这显然会降低性能,如果许多键都有相同的索引,就有可能出现崩溃.根据Microsoft的部分算法工作的方式,在计算出来的散列值平均分布在int.MinValue和int.MaxValue之间时,这种崩溃的危险会被降低到最小.

字典的元素最多,键之间的崩溃危险也就越大,所以最好确保字典的容量大于其中的元素个数.因此,在字典被填满之前,Hashtable会自动重新分配内存,增大其容量.表被填满的比例称为负载(load),可以为这个负载设置一个最大值,在另一个Hashtable构造函数中给Hashtable重新分配内存前,该负载会达到这个最大值:

//capacity = 50,MaxLoad=0.5

Hashtable Employees = new Hashtable(50,0.5)

负载的最大值越小,散列表的工作次序就越高,但它占据的内存也越大.当散列表为了增大其容量而重新分配内存时,总会选择一个素数作为其新容量.

上面列出的另一个要点是,散列算法必须一致.如果两个对象包含相同的数据,它们就必须给出相同的散列值,这就是System.Object的Equals()和GetHashCode()方法时必须遵循的重要限制,Hashtable确定两个键A和B是否相等的方式就是调用A.Equals(B),即必须确保下述条件总是true:

如果A.Equals(B)是true,则A.GetHashCode()和B.GetHashCode()必须返回相同的散列.

这看起来可能非常微妙,但非常重要.如果采用重写这引起方法的方式不能保证上述语句为true,用这个类实例作为键的散列表就不可能正常工作.例如,把一个对象放在散列表中,但从来没有猎取过它,或者试图获取一个条目,但会返回错误的条目.

因此,如果为Equals()提供了一个重写方法,但没有为GetHashCode()提供重写方法,C#编译器就会显示一个编译警告.

对于System.Object,这个条件为true,因为Equals()仅比较引用,GetHashCode会根据对象的地址返回一个散列,则基于健步如飞的散列表没有重写这些方法,将正常工作.但是,这种方式也有问题,只有键是相同的对象时,才是相等的.当把一个对象放在字典中时,就必须挂起键的引用.以后也不能实例化另一个有相同值的键对象,国为相同的什被定义为表示相同的实例.如果没有重写Equals()和GetHashCode()和Object版本,类就不能在散列表中方便的使用因此,应根据键的值来执行GetHashCode(),生成一个散列,而不是根据内存中的地址来执行该方法,这就是必须为要用作键的类重写Equals()的原因.

System.String有三个重载方法,重载Equals()提供了值的对比,GetHashCode()也被相应地重载,根据字符串的值返回一个散列.因此,字典中把字符串用作键是非常方便的.

原创粉丝点击