源代码版本为 .NET Framework 4.6.1
本系列持续更新,敬请关注
有投入,有产出。
(注:非基础性,主要涉及Dictionary的实现原理)
水平有限,若有不对之处,望指正。
Dictionary是Hashtable的一种泛型实现(也是一种哈希表)实现了IDictionary反应接口和非泛型接口等,将键映射到相应的值。任何非 null 对象都可以用作键。使用与Hashtable不同的冲突解决方法,Dictionary使用拉链法。
概念重播
对于不同的关键字可能得到同一哈希地址,即key1 != key2 => F(key1)=F(fey2),这种现象叫做冲突,在一般情况下,冲突只能尽可能的少,而不能完全避免。因为,哈希函数是从关键字集合到地址集合的映像。通常,关键字集合比较大,它的元素包括多有可能的关键字。既然如此,那么,如何处理冲突则是构造哈希表不可缺少的一个方面。
通常用于处理冲突的方法有:开放定址法、再哈希法、链地址法、建立一个公共溢出区等。
在哈希表上进行查找的过程和哈希造表的过程基本一致。给定K值,根据造表时设定的哈希函数求得哈希地址,若表中此位置没有记录,则查找不成功;否则比较关键字,若何给定值相等,则查找成功;否则根据处理冲突的方法寻找“下一地址”,知道哈希表中某个位置为空或者表中所填记录的关键字等于给定值时为止。
哈希函数
Dictionary使用的哈希函数是除留余数法,在源码中的公式为:
h = F(k) % m; m 为哈希表长度(这个长度一般为素数)
通过给定或默认的GetHashCode()函数计算出关键字的哈希码模以哈希表长度,计算出哈希地址。
拉链法
Dictionary使用的解决冲突方法是拉链法,又称链地址法。
拉链法的原理:将所有关键字为同义词的结点链接在同一个单链表中。若选定的散列表长度为m,则可将散列表定义为一个由m个头指针组成的指针数 组T[0..m-1]。凡是散列地址为i的结点,均插入到以T[i]为头指针的单链表中。T中各分量的初值均应为空指针。结构图大致如下:
基本成员
private struct Entry {
public int hashCode; //31位散列值,32最高位表示符号位,-1表示未使用
public int next; //下一项的索引值,-1表示结尾
public TKey key; //键
public TValue value; //值
}
private int[] buckets;//内部维护的数据地址
private Entry[] entries;//元素数组,用于维护哈希表中的数据
private int count;//元素数量
private int version;
private int freeList;//空闲的列表
private int freeCount;//空闲列表元素数量
private IEqualityComparer<TKey> comparer;//哈希表中的比较函数
private KeyCollection keys;//键集合
private ValueCollection values;//值集合
private Object _syncRoot;
buckets 就想在哈希函数与entries之间解耦的一层关系,哈希函数的F(k)变化不在直接影响到entries。
freeList 类似一个单链表,用于存储被释放出来的空间即空链表,一般有被优先存入数据。
freeCount 空链表的空位数量。
初始化函数
该函数用于,初始化的数据构造
private void Initialize(int capacity) {
//根据构造函数设定的初始容量,获取一个近似的素数
int size = HashHelpers.GetPrime(capacity);
buckets = new int[size];
for (int i =