列出<t>容量增加与字典<k,v>容量增加?</k,v> </t>

时间:2013-01-30 08:08:17

标签: c# .net

为什么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?两者都在处理找到继续记忆位置 ...正确吗?

6 个答案:

答案 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)。 (验证/得出这个是微不足道的)。现在,您可以执行以下操作之一以避免群集:

  1. 确保您不会生成太多的hashCode,这些hashCode是{x,2x,3x,4x,5x,6x ...}中的另一个hashCode的倍数。但如果这可能有点困难,如果你的hashTable应该有数百万条目。

  2. 或者通过使GreatestCommonFactor(table_length,x)等于1来简单地使m等于table_length,即通过使table_length与x进行互操作。如果x可以是任何数字,那么请确保table_length是素数。

  3. (来自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)

我不是计算机科学家,但是......

最可能的是它与HashTableLoad 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的复杂性。 DictionaryHashMapList分别以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并确保能力是最重要的。