为什么List<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;
}
}
为什么Dictionary<K,V>
使用素数作为容量?
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;
}
为什么它也不会乘以2?两者都在处理找到继续记忆位置 ...正确吗?
答案 0 :(得分:1)
Dictionary将其所有对象放入桶中,具体取决于它们的GetHashCode值,即
Bucket[object.GetHashCode() % DictionarySize] = object;
它使用素数来避免碰撞的机会。据推测,具有许多除数的大小对于设计不良的哈希码是不好的。
答案 1 :(得分:1)
将哈希表大小用于素数是很常见的,因为它可以降低冲突的概率。
哈希表通常使用模运算来查找条目所属的存储区,如代码所示:
int index = destinationArray[j].hashCode % prime;
假设你的hashCode函数导致以下hashCodes {x,2x,3x,4x,5x,6x ...},那么所有这些都将聚集在m个桶中,其中m = table_length / GreatestCommonFactor(table_length,x)。 (验证/得出这个是微不足道的)。现在,您可以执行以下操作之一以避免群集:
确保您不会生成太多的hashCode,这些hashCode是{x,2x,3x,4x,5x,6x ...}中的另一个hashCode的倍数。但如果这可能有点困难,如果你的hashTable应该有数百万条目。
或者通过使GreatestCommonFactor(table_length,x)等于1来简单地使m等于table_length,即通过使table_length与x进行互操作。如果x可以是任何数字,那么请确保table_length是素数。
(来自http://srinvis.blogspot.com/2006/07/hash-table-lengths-and-prime-numbers.html)
HashHelpers.GetPrime(this.count * 2)
应返回素数。看看HashHelpers.GetPrime()的定义。
答案 2 :(得分:1)
来自SO中的question;
字典或散列表依赖于哈希键来获得更小的密钥 查找相应商店(数组)的索引。所以哈希的选择 功能非常重要。典型的选择是获取a的哈希码 key(以便我们获得良好的随机分布)然后划分代码 通过素数并使用提醒索引到固定数量 桶。这允许将任意大的哈希码转换为a 有限的一组小数字,我们可以定义一个数组来查看 进入。因此,在素数中使用数组大小非常重要 尺寸的最佳选择成为更大的素数 比所需的容量。那就是字典 实施确实。
List<T>
使用array
来存储数据;并且增加阵列的容量需要将阵列复制到新的存储器位置;这很费时间。我想,为了降低复制数组的出现次数,列表会将其容量加倍。
答案 3 :(得分:1)
我不是计算机科学家,但是......
最可能的是它与HashTable的Load factor(最后一个链接只是一个数学定义)相关,并且由于没有产生更多的混淆,因为不是数学听觉,重要的是要定义: / p>
loadFactor = FreeCells/AllCells
这个我们可以写成
loadFactor = (AllBuckets - UsedBuckets)/AllBuckets
loadFactor
在哈希映射中定义了碰撞的概率。
所以使用Prime Number,一个
..是一个大于1的自然数 除了1和它本身之外没有正的除数。
我们在hashmap中减少(但不要删除)碰撞风险。
如果loadFactor
趋于0
,我们会有更安全的hashmap,因此我们必须尽可能保持最低限度。通过MS blog,他们发现loadFactor
(最优的一个)的值必须是0.72
,所以如果它变大,我们会增加最接近素数的容量。
编辑
要更清楚这一点:拥有素数,尽可能确保散列在我们在.NET字典中的哈希的具体实现中的散列的均匀分布。它不是关于值的检索效率,而是关于所用内存的效率和降低碰撞风险。
希望这有帮助。
答案 4 :(得分:1)
Dictionary
需要一些启发式方法,以便在桶之间分配哈希码更加均匀。
.NET的Dictionary
使用素数桶来做,然后像这样计算桶索引:
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的幂,并且在增长时它只是桶的数量加倍:
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
另一方面, List
不需要任何启发式,所以他们没有打扰。
添加:成长行为根本不会影响Add
的复杂性。 Dictionary
,HashMap
和List
分别以Add
的O(1)复杂度摊销。
成长操作需要O(N),但只发生第N次,所以为了引起成长操作,我们需要调用Add
N次。对于N = 8,执行N Add
s所需的时间具有值
O(1)+ O(1)+ O(1)+ O(1)+ O(1)+ O(1)+ O(1)+ O(N)= O(N)+ O( N)= O(2N)= O(N)
所以,N Add
取O(N),然后一Add
取O(1)。
答案 5 :(得分:0)
在调整大小时需要通过常数因子(而不是例如通过添加常数增加容量)来增加容量,以保证一些摊销的运行时间。例如,添加到基于数组的列表末尾或从基于列表的末尾删除需要O(1)
时间,除非您必须增加或减少需要复制列表内容的容量,因此需要O(n)
时间。通过常量因子更改容量可确保分摊的运行时仍为O(1)
。因子的最佳值取决于预期的使用。有关Wikipedia的更多信息。
选择哈希表的容量作为素数用于改善项目的分布。如果bucket[hash % capacity]
为hash
,则capacity
不均匀分布,{{1}}会产生更均匀的分布。 (我不能在后面给出数学但是我正在寻找一个很好的参考。)这与第一点的结合正是实现的作用 - 将容量增加一个因子(至少)2并确保能力是最重要的。