何时扩容
Dictionary内部实际上是维护了两个数组对数据进行保存,既然是数组,那么自然会有数组放满的风险,根据前面的Add方法源码的分析,可以看到其中有一行当count(空闲位置)== hash桶长度时,会进行扩容操作。
这是扩容比较直观的触发条件。在另外一种情况下Dictionary也会触发扩容,这就是当碰撞次数过多时,它会自动扩容。
那么什么是碰撞呢?
在FindEntry方法中,当我们沿着同类冲突元素的单链表一直找下去的时候,每遍历一个元素,碰撞次数collisionCount就+1,当碰撞次数超过设定好的阈值时,就会触发扩容。
FindEntry方法源码中触发碰撞的代码:
private int FindEntry(TKey key)
{
....
// 碰撞次数
int collisionCount = 0;
...
do
{
if ((uint)i >= (uint)entries.Length || (entries[i].hashCode == hashCode && EqualityComparer<TKey>.Default.Equals(entries[i].key, key)))
{
break;
}
i = entries[i].next;
if (collisionCount >= entries.Length)
{
ThrowHelper.ThrowInvalidOperationException_ConcurrentOperationsNotSupported();
}
// 在遍历这个单链表的时候,每查找失败一次,碰撞次数+1
collisionCount++;
} while (true);
...
}
很显然这个碰撞次数就关系到了我们前面说的查找方法的消耗时间,所以当碰撞次数超过阈值,Dictionary就自动扩容了,并且在扩容后还会使用新的Hash函数来进行HashCode值。
version(版本)和迭代的关系
在C#中,增、删、改操作都会使得Dictionary中的version(版本)变量改变。
这里依旧引用一下@InCerry大大的博文对于这个的解释。
迭代过程中不允许集合出现变化。如果在Java中遍历直接删除元素,会出现诡异的问题,所以.Net中就使用了version来实现版本控制。
那么如何在迭代过程中实现版本控制的呢?我们看一看源码就很清楚的知道。
在迭代器初始化时,就会记录dictionary.version版本号,之后每一次迭代过程都会检查版本号是否一致,如果不一致将抛出异常。
Dictionary的增删查改效率如何?
经过上面的分析,我们就可以知道Dictionary的增删改查效率了,列表如下:
Dictionary |
O(1) |
O(1) |
O(1) |
O(1) |
可以看到字典这个数据结构还是相当给力的,基本的操作流程基本都是O(1)的复杂度,不过它会消耗更多的空间就是了(毕竟使用了两个数组进行维护)。
总结
这篇博文也是写的相当久,因为我基础不是很牢固,一开始写这个系列的就是为了巩固下C#基础,所以可能出现了一些错误,如果有读者看到,还请不吝指出,我会非常感激的。(话说上一篇String和StringBuilder的错误还没改。。懒癌发作。。)
本篇文章后半段关于Dictionary的部分大部分是参考@InCerry的博文【浅析C# Dictionary实现原理】(讲的相当清楚)所写的,emmmm,如果有什么版权方面的问题,请联系我,我会立即删除这篇文章。