【问题标题】:List<T> and ArrayList default capacityList<T> 和 ArrayList 默认容量
【发布时间】:2010-12-12 12:37:08
【问题描述】:

我一直在使用Reflector 研究 .NET 的 ListArrayList 实现。

在查看 Add(T item) 时,我遇到了这个问题。EnsureCapacity(this._size + 1):

public void Add(T item)
{
    if (this._size == this._items.Length)
    {
       this.EnsureCapacity(this._size + 1);
    }
    this._items[this._size++] = item;
    this._version++;
}

因此 EnsureCapacity 看起来像这样:

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;
    }
}

为什么内部容量默认为 4,然后以 2 的倍数递增(即:double)?

【问题讨论】:

标签: c# .net performance algorithm memory-management


【解决方案1】:

4 是一个很好的默认值,因为大多数集合中只有少数项目。递增是为了确保您不会在每次添加项目时都进行内存分配。

请参阅 Joel 撰写的这篇关于内存使用的好文章,以及为什么分配双倍所需的内存是个好主意。

http://www.joelonsoftware.com/printerFriendly/articles/fog0000000319.html

以下是相关引述:

假设您编写了一个自动重新分配目标缓冲区的智能 strcat 函数。它是否应该始终将其重新分配到所需的确切大小?我的老师兼导师 Stan Eisenstat 建议,当您调用 realloc 时,您应该始终将之前分配的内存大小加倍。这意味着您永远不必调用 realloc 超过 lg n 次,即使对于大字符串也具有不错的性能特征,并且您永远不会浪费超过 50% 的内存。

顺便说一句,列表 和字典 现在默认为 10,但我敢打赌它们具有相同的递增逻辑。

【讨论】:

  • 它也是 Microsoft 的 CLR 实现定义的行为。 (但似乎不是 ECMA-364 的要求。)
  • @chris.w.mclean:虽然在需要调整大小时将容量加倍确实可以确保您不会在每次添加项目时都进行内存分配,但这并不是确保这一点的唯一策略。例如,我可以通过在每次需要调整大小时将容量增加 2 来调整大小。这条评论的重点是,避免每次添加项目时重新分配并不是通过加倍调整大小的唯一目标。
  • 如果您最终调整大小一次,您可能需要它不止 2 次。我敢打赌,MS 内部的研究表明,这有指数级的使用(指数增长,例如加倍)。我敢打赌,也有研究表明 10/4 是很好的初始值。
  • @Jason,是的,你是对的,这就是为什么我把 Joel 的链接放在那里,说明为什么加倍策略是正确的。他解释得比我好得多……我还敢打赌,来自 com 和 VB 的真实世界内存使用研究表明,4 是一个很好的起点,而 .net 1.0 /1.1 检测代码的另一系列研究这表明他们 10 可能是一个更好的新默认值。
【解决方案2】:

关于需要调整大小时加倍的原因如下。假设您要将n 项目插入List。我们将调整列表的大小不超过log n 次。因此,将n 项插入List 将花费最坏情况下的O(n) 时间,因此amortized time 中的插入是恒定的。此外,浪费的空间量以n 为界。任何恒定比例增长的策略都将导致恒定的分摊插入时间和线性浪费空间。比恒定比例增长更快的增长可能会导致更快的插入,但会浪费更多的空间。比恒定比例增长慢的增长可能会减少浪费的空间,但代价是插入速度较慢。

默认容量是为了浪费很少的内存(从小处开始并没有什么坏处,因为从时间/空间的角度来看,我们刚刚看到的双倍大小调整策略是好的)。

【讨论】:

    【解决方案3】:

    似乎 4 是在足够大以适应具有 4 个或更少物品的频繁场景与没有太多浪费物品之间的合理权衡。

    每增加一次分配,容量就会翻倍,确保它可以容纳容器中已有物品数量的两倍。这是一个类似于 C++ 的向量容器的算法。

    【讨论】:

    • 事实:它不会“指数”增长,但会“倍增”(两倍)。
    • @opello:这就是指数增长的定义。恒定的比例增长率。​​span>
    【解决方案4】:

    我敢打赌,这样您就可以创建无需多次分配的小型列表。将大小翻倍是为了简单,而不是使用复杂的缩放算法。

    【讨论】:

    • 底层的 malloc 可能也喜欢 2 的倍数,所以它不仅仅是简单。
    • 我认为这是隐含的,因为 2 的倍数在计算中非常常见。
    猜你喜欢
    • 2011-05-12
    • 1970-01-01
    • 1970-01-01
    • 2012-06-03
    • 1970-01-01
    • 2013-12-19
    • 1970-01-01
    • 2013-06-24
    • 2014-09-07
    相关资源
    最近更新 更多