你真的了解字典(Dictionary)吗? (2)

这就导致同一个bucket可能有多个key对应,即下图中的Johon Smith和Sandra Dee,但是bucket只能记录一个内存地址(索引),也就是警察叔叔通过家庭地址找到我家时,正常来说,只有一个人过来开门,那么,如何找到也在这个家里的我的呢?我爸记录这我妈在厨房,我妈记录着我在书房,就这样,我就被揪出来了,我爸,我妈,我 就是字典中的一个entry.

Alt text


如果有一天,我妈妈老来得子又生了一个小宝宝,怎么办呢?很简单,我妈记录小宝宝的位置,那么我的只能巴结小宝宝,让小宝宝来记录我的位置了.

Alt text


Alt text

既然大的原理明白了,是不是要看看源码,来研究研究代码中字典怎么实现的呢?

DictionaryMini

上次在苏州参加苏州微软技术俱乐部成立大会时,有幸参加了蒋金楠 老师讲的Asp .net core框架解密,蒋老师有句话让我印象很深刻,"学好一门技术的最好的方法,就是模仿它的样子,自己造一个出来"于是他弄了个Asp .net core mini,所以我效仿蒋老师,弄了个DictionaryMini

其源代码我放在了Github仓库,有兴趣的可以看看:https://github.com/liuzhenyulive/DictionaryMini

我觉得字典这几个方面值得了解一下:

数据存储的最小单元的数据结构

字典的初始化

添加新元素

字典的扩容

移除元素

字典中还有其他功能,但我相信,只要弄明白的这几个方面的工作原理,我们也就恰中肯綮,他么问题也就迎刃而解了.

数据存储的最小单元(Entry)的数据结构 private struct Entry { public int HashCode; public int Next; public TKey Key; public TValue Value; }

一个Entry包括该key的HashCode,以及下个Entry的索引Next,该键值对的Key以及数据Vaule.

字典初始化 private void Initialize(int capacity) { int size = HashHelpersMini.GetPrime(capacity); _buckets = new int[size]; for (int i = 0; i < _buckets.Length; i++) { _buckets[i] = -1; } _entries = new Entry[size]; _freeList = -1; }

字典初始化时,首先要创建int数组,分别作为buckets和entries,其中buckets的index是key的哈希值%size,它的value是数据在entries中的index,我们要取的数据就存在entries中.当某一个bucket没有指向任何entry时,它的value为-1.
另外,很有意思得一点,buckets的数组长度是多少呢?这个我研究了挺久,发现取的是大于capacity的最小质数.

添加新元素 private void Insert(TKey key, TValue value, bool add) { if (key == null) { throw new ArgumentNullException(); } //如果buckets为空,则重新初始化字典. if (_buckets == null) Initialize(0); //获取传入key的 哈希值 var hashCode = _comparer.GetHashCode(key); //把hashCode%size的值作为目标Bucket的Index. var targetBucket = hashCode % _buckets.Length; //遍历判断传入的key对应的值是否已经添加字典中 for (int i = _buckets[targetBucket]; i > 0; i = _entries[i].Next) { if (_entries[i].HashCode == hashCode && _comparer.Equals(_entries[i].Key, key)) { //当add为true时,直接抛出异常,告诉给定的值已存在在字典中. if (add) { throw new Exception("给定的关键字已存在!"); } //当add为false时,重新赋值并退出. _entries[i].Value = value; return; } } //表示本次存储数据的数据在Entries中的索引 int index; //当有数据被Remove时,freeCount会加1 if (_freeCount > 0) { //freeList为上一个移除数据的Entries的索引,这样能尽量地让连续的Entries都利用起来. index = _freeList; _freeList = _entries[index].Next; _freeCount--; } else { //当已使用的Entry的数据等于Entries的长度时,说明字典里的数据已经存满了,需要对字典进行扩容,Resize. if (_count == _entries.Length) { Resize(); targetBucket = hashCode % _buckets.Length; } //默认取未使用的第一个 index = _count; _count++; } //对Entries进行赋值 _entries[index].HashCode = hashCode; _entries[index].Next = _buckets[targetBucket]; _entries[index].Key = key; _entries[index].Value = value; //用buckets来登记数据在Entries中的索引. _buckets[targetBucket] = index; } 字典的扩容 private void Resize() { //获取大于当前size的最小质数 Resize(HashHelpersMini.GetPrime(_count), false); } private void Resize(int newSize, bool foreNewHashCodes) { var newBuckets = new int[newSize]; //把所有buckets设置-1 for (int i = 0; i < newBuckets.Length; i++) newBuckets[i] = -1; var newEntries = new Entry[newSize]; //把旧的的Enties中的数据拷贝到新的Entires数组中. Array.Copy(_entries, 0, newEntries, 0, _count); if (foreNewHashCodes) { for (int i = 0; i < _count; i++) { if (newEntries[i].HashCode != -1) { newEntries[i].HashCode = _comparer.GetHashCode(newEntries[i].Key); } } } //重新对新的bucket赋值. for (int i = 0; i < _count; i++) { if (newEntries[i].HashCode > 0) { int bucket = newEntries[i].HashCode % newSize; newEntries[i].Next = newBuckets[bucket]; newBuckets[bucket] = i; } } _buckets = newBuckets; _entries = newEntries; } 移除元素 //通过key移除指定的item public bool Remove(TKey key) { if (key == null) throw new Exception(); if (_buckets != null) { //获取该key的HashCode int hashCode = _comparer.GetHashCode(key); //获取bucket的索引 int bucket = hashCode % _buckets.Length; int last = -1; for (int i = _buckets[bucket]; i >= 0; last = i, i = _entries[i].Next) { if (_entries[i].HashCode == hashCode && _comparer.Equals(_entries[i].Key, key)) { if (last < 0) { _buckets[bucket] = _entries[i].Next; } else { _entries[last].Next = _entries[i].Next; } //把要移除的元素置空. _entries[i].HashCode = -1; _entries[i].Next = _freeList; _entries[i].Key = default(TKey); _entries[i].Value = default(TValue); //把该释放的索引记录在freeList中 _freeList = i; //把空Entry的数量加1 _freeCount++; return true; } } } return false; }

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/zyywwj.html