江枫

哈希表 & List & Dictionary

1.List

List源码

List可以认为是一个可以自动扩容的数组,每次扩容后容量翻倍

构造函数:

Add函数(自动扩容):

Remove和Inset函数并不是单纯的清理值,而是进行数组的copy:

2.哈希函数与哈希表

哈希函数:

将哈希表中元素的关键键值映射为元素存储位置的函数。

哈希表:

结构中存储了key的哈希值的数据结构。

一般的链表、树结构中存储位置是随机的,查找起来效率低,哈希表内存储了哈希值,可以快速计算要查找项的哈希值,并查找对应数据。

举个栗子:

    我们现在需要存储一个键值对数据A-B,key为A,value为B。但是A和B所占用的内存较大,而且查找的时候比对也很困难。

    这个时候我们可以使用哈希表来增加索引速度:将A通过哈希函数计算得到A的哈希值,然后将A的哈希值和A都存在哈希表内,这样在查找的时候就可以直接计算要查找目标的哈希值然后去哈希表内进行比对,就可以快速查找到对应数据。

哈希表的冲突:

key的哈希值是通过哈希函数计算得到的,那么就难以避免的会出现不同的key值通过哈希函数后得到相同的哈希值。

这种情况我们成为哈希冲突,解决哈希冲突的方法比较多,如拉链法,多哈希法,开放地址法等等。不过目前只有拉链法可以完全解决哈希冲突的问题。

拉链法:

主要思想就是在存储时使用一个额外的动态链表代替顺序存储结构。

以C#的Dictionary为例,拉链法的实现步骤为:

1.计算Key的HashValue

2.根据 HashValue 定位到 table[hashIndex](table[hashIndex] 为一个链表的Node)

3.如果 table[hashIndex] 为空则直接插入,不然则添加到链表末尾

3.Dictionary

数据存储与构造函数:

可以看到初始化的时候主要就是初始化了size、buckets和entries。

Entry:拉链表中动态链表的节点,也就是上面说的Node

buckets与entries:buckets存储哈希值对应到的Entry的索引,entries则为node的存储集合。

添加节点

可以看到:当来了一个key-value要存储时,先通过哈希函数计算key的哈希值,然后再计算targetBucket,即这个哈希值应该放到哪个buckets桶内(其实就是计算的哈希值与buckets长度取余),比如这里计算后的结果是1号桶。

通过buckets[1]可以拿到对应节点Entry的ID,然后entries[EntryID]就可以拿到动态链表的头节点。

最后添加节点到链表内:也就是构建一个新的Entry,然后这个Entry的next指向之前buckets[1]的EntryID,然后buckets[1]内缓存新构建的EntryID就可以了。

查找节点

其实不用看代码也能知道,我们把一个很长的线性表拆成了几份小的线性表,并通过哈希值进行快速查找,从而能更快的查找到结果。

代码也比较简单,就是找到对应的动态链表头节点,然后依次比对哈希值和对应key值就知道是不是查找到了。

4.其他的哈希表优化

Dictionary中除了存储key值的哈希值,也缓存了要存储的key-value本身。但是有的时候为了优化资源减少内存,我们也可以使用哈希表进行内存优化。

例如:翻译表的格式为Key-Value键值对(中文-其他语言),可以计算key的哈希值,并在缓存数据时使用哈希值作为key进行存储,查找时也将要查找的key转换为哈希值后再进行匹配。

文章大纲