C# 列表<;T>;容量增加vs字典<;K、 V>;产能增加?
为什么C# 列表<;T>;容量增加vs字典<;K、 V>;产能增加?,c#,.net,C#,.net,为什么列表的容量增加了2倍 private void EnsureCapacity(int min) { if (this._items.Length < min) { int num = (this._items.Length == 0) ? 4 : (this._items.Length * 2); if (num < min) { num = min; } t
列表
的容量增加了2倍
private void EnsureCapacity(int min)
{
if (this._items.Length < min)
{
int num = (this._items.Length == 0) ? 4 : (this._items.Length * 2);
if (num < min)
{
num = min;
}
this.Capacity = num;
}
}
private void-rescapacity(int-min)
{
如果(此项长度
为什么字典
使用素数作为容量
private void Resize()
{
int prime = HashHelpers.GetPrime(this.count * 2);
int[] numArray = new int[prime];
for (int i = 0; i < numArray.Length; i++)
{
numArray[i] = -1;
}
Entry<TKey, TValue>[] destinationArray = new Entry<TKey, TValue>[prime];
Array.Copy(this.entries, 0, destinationArray, 0, this.count);
for (int j = 0; j < this.count; j++)
{
int index = destinationArray[j].hashCode % prime;
destinationArray[j].next = numArray[index];
numArray[index] = j;
}
this.buckets = numArray;
this.entries = destinationArray;
}
private void Resize()
{
int prime=HashHelpers.GetPrime(this.count*2);
int[]numaray=新的int[prime];
for(int i=0;i
为什么不直接乘以2呢?两者都在处理查找内存位置的问题…对吗 字典根据对象的GetHashCode值将其所有对象放入bucket中,即
Bucket[object.GetHashCode()%DictionarySize]=object代码>
它使用质数表示大小,以避免发生碰撞。假设一个有许多除数的大小对于设计糟糕的哈希代码来说是不好的。对于哈希表大小使用素数是很常见的,因为它降低了冲突的概率
哈希表通常使用模运算来查找条目所属的bucket,如代码中所示:
int index = destinationArray[j].hashCode % prime;
假设您的hashCode函数产生以下hashCode以及其他{x,2x,3x,4x,5x,6x…},那么所有这些都将聚集在m个bucket中,其中m=table_length/GreatestCommonFactor(table_length,x)。(验证/推导这一点很简单)。现在,您可以执行以下操作之一以避免群集:
请确保您不会生成太多的哈希代码,这些哈希代码是{x,2x,3x,4x,5x,6x…}中另一个哈希代码的倍数。但是如果您的哈希表应该有数百万个条目,那么这可能有点困难
或者简单地通过使最大公因子(table_length,x)等于1,即通过使table_length与x互质,使m等于table_length。如果x可以是任意一个数,那么确保table_length是一个素数
(来自)
应该返回一个素数。看看HashHelpers.GetPrime()的定义。来自SO中的a
字典或哈希表依赖于对键进行哈希运算以获得较小的值
用于查找相应存储(数组)的索引。那么哈希的选择呢
功能非常重要。典型的选择是获取
键(以便获得良好的随机分布),然后分割代码
由一个素数,并使用提醒索引到固定数
水桶。这允许将任意大的哈希代码转换为
有界的一组小数字,我们可以为其定义一个数组来查找
一直到。所以让数组大小为素数很重要,然后
对于大小的最佳选择是变大的素数
超过所需容量。这就是字典
实现是这样的
List
使用array
s存储数据;增加阵列的容量需要将阵列复制到新的内存位置;这很费时。我想,为了降低复制数组的发生率,list将其容量加倍。我不是计算机科学家,但
最有可能的是它与a有关(最后一个链接只是一个数学定义),为了不造成更多的混乱,因为不是数学听觉,重要的是定义:
loadFactor = FreeCells/AllCells
我们可以这样写
loadFactor = (AllBuckets - UsedBuckets)/AllBuckets
loadFactor
定义哈希映射中的冲突概率。
所以通过使用一个数字
…是大于1的自然数
除1和自身外,没有其他正因子
我们减少(但不消除)hashmap中的冲突风险
如果loadFactor
趋向于0
,我们有更安全的hashmap,因此我们总是必须将其保持在尽可能低的水平。通过MS,他们发现加载因子
(最佳值)的值必须在0.72
附近,因此,如果它变大,我们会根据最近的素数增加容量
编辑
更清楚地说:有一个素数,就可以确保在.NET字典中哈希的具体实现中,哈希尽可能的均匀分布。这不是关于检索值的效率,而是使用内存的效率和碰撞风险的降低
希望这有帮助。字典
需要一些启发,以便哈希代码在存储桶之间的分布更加均匀
.NET的字典
使用存储桶的素数来实现这一点,然后计算存储桶索引,如下所示:
int num = this.comparer.GetHashCode(key) & 2147483647; // make hash code positive
// get the remainder from division - that's our bucket index
int num2 = this.buckets[num % ((int)this.buckets.Length)];
当它增长时,它会将桶的数量增加一倍,然后再增加一些,使这个数字再次成为素数
这不是唯一可能的启发式方法。例如,Java的HashMap
采用了另一种方法。存储桶的数量始终是2的幂,在增长时,它只是存储桶数量的两倍:
但在计算桶索引时,它会修改哈希:
static int hash(int h) {
// This function ensures that hashCodes that differ only by
// constant multiples at each bit position have a bounded
// number of collisions (approximately 8 at default load factor).
h ^= (h >>> 20) ^ (h >>> 12);
return h ^ (h >>> 7) ^ (h >>> 4);
}
static int indexFor(int h, int length) {
return h & (length-1);
}
// from put() method
int hash = hash(key.hashCode()); // get modified hash
int i = indexFor(hash, table.length); // trim the hash to the bucket count
另一方面,List
不需要任何启发,所以他们不需要麻烦
添加:增长行为根本不会影响添加的复杂性<代码>字典,哈希映射
和列表
都摊销了O(1)的复杂性
Grow操作需要O(N),但只发生第N次,因此要引起Grow操作,我们需要调用resize(2 * table.length);
static int hash(int h) {
// This function ensures that hashCodes that differ only by
// constant multiples at each bit position have a bounded
// number of collisions (approximately 8 at default load factor).
h ^= (h >>> 20) ^ (h >>> 12);
return h ^ (h >>> 7) ^ (h >>> 4);
}
static int indexFor(int h, int length) {
return h & (length-1);
}
// from put() method
int hash = hash(key.hashCode()); // get modified hash
int i = indexFor(hash, table.length); // trim the hash to the bucket count