【问题标题】:C# - What is List<T> initial capacity used for? [duplicate]C# - List<T> 初始容量用于什么? [复制]
【发布时间】:2014-10-15 06:47:13
【问题描述】:

我正在尝试执行以下操作:

int count = 50;
List<float> myList = new List<float>(50);
output[0] = 0.0f;

但我得到了错误:

ArgumentOutOfRangeException: Argument is out of range.
Parameter name: index

很明显,我误解了 List(或任何其他集合?)的初始容量。有人可以向我解释一下初始容量的用途吗?

【问题讨论】:

  • @rageit 默认 size 为 0。我认为默认容量类似于 16。 (凭记忆。)
  • 什么是output?您已经定义了大小为 50 的 myList
  • @TimothyShields 感谢您的纠正。大多数框架的默认容量似乎为 0 - stackoverflow.com/questions/1762817/default-capacity-of-list
  • @rageit ArgumentOutOfRangeException 被抛出,因为没有添加任何内容,并且尝试从索引 0 访问某些内容。容量与它没有任何关系。

标签: c# list capacity


【解决方案1】:

列表的底层有一个数组。当您添加多达 50 个元素时,这会减少内存重新分配的数量。这需要时间和内存,并让垃圾收集器有工作要做。

这就是为什么List(T).Capacity 是一个东西。

Here are some benchmarks 为 100 .Adds:

 Method A: Dictionary, no capacity
 Time:     1350 ms

Method B: Dictionary, has capacity
Time:     700 ms

Method C: Dictionary, const capacity
Time:     760 ms

Method D: Dictionary, over-large capacity
Time:     1005 ms

Method E: List, no capacity
Time:     1010 ms

Method F: List, accurate capacity
Time:     575 ms

因此,当您添加 100 个元素时,预分配时间似乎减少了一半。虽然我不喜欢过早优化,但如果你知道你的列表会有多大,给 CLR 一个提示听起来很值得。

【讨论】:

  • +1 基准测试的好例子
  • 感谢简洁的解释!
【解决方案2】:

用于预分配列表使用的内部数组。 (这个内部数组的大小由List.Capacity给出。)

但是,仅仅因为这个内部数组具有一定的大小,并不意味着列表中的那些元素是可用的;它们不是,直到您(例如)使用List.Add() 添加适当的元素。列表的当前可寻址大小由List.Count 给出。

当你向列表中添加一个新元素时,它首先检查内部数组中是否有足够的空间,如果有,它只是增加一个内部计数器并使用内部数组中的一个槽。

如果没有足够的空间,它会分配一个两倍于旧缓冲区大小的新缓冲区,将旧缓冲区中的所有元素复制到新缓冲区中,然后丢弃旧缓冲区。然后它可以使用新缓冲区中的一个槽。这显然是一项相当昂贵的操作。

通过设置初始大小,您可以避免部分(或全部)缓冲区重新分配。

当您知道列表的最大可能大小但不知道最小大小,并且您希望在执行尽可能少的重新分配的同时填充列表时,这是一种常见用途。

【讨论】:

    【解决方案3】:

    列表是动态的。它可以在运行时动态添加和删除项目。
    List 实现使用底层数组来存储列表的项目。 底层数组的长度称为列表的容量
    列表中的每一项都存储在底层数组中。
    当尝试将新项目添加到列表中并且基础数组中没有更多空格时,将创建一个新的更大的数组。
    旧数组中的所有项目都将被移动到新数组中,其中还包含更多空间用于新项目。
    然后将新数组设置为列表的底层数组(旧数组将被丢弃)。

    分配一个新数组然后将项目从旧数组移动到新数组的操作代价高昂(性能方面)。

    因此,如果您从一开始就知道要添加多少项到列表中。那么您可以使用具有所需初始容量的基础数组创建列表。
    因此,您的列表在运行时分配新底层数组的可能性会更小。
    您可以通过调用 List(T) Constructor(int32)
    来设置底层数组的初始长度 可以通过调用List(T).Capacity Property获取当前数组的长度

    【讨论】:

      【解决方案4】:

      列表仍然是空的,因为没有元素,但在内部它为 50 个项目保留了内存。这是一种优化,因为当您将项目添加到列表时,它必须分配一个两倍大小的新数组,然后将项目从旧数组复制到新数组。

      请注意,在此转换过程中,例如从 256 个项目开始再添加一个,当它复制到新数组时,它总共分配了 (256+512) 768 个项目的内存。基本上是以前容量的三倍。根据数组的大小,这可能会导致内存不足异常。因此,如果您预先知道您只会将 257 个项目添加到列表中,那么您可以预先使用 257 的容量。这将避免这种三倍的内存使用,因为数组已经足够大,因此不必增加大小。请注意,由于在未压缩的堆上分配了非常大的数组,因此发生内存不足异常的可能性会增加,因此碎片会导致难以为整个数组找到连续内存块的情况.因此,当您似乎有足够的可用内存时,有时此问题会导致您遇到内存不足异常。当然,如果可以的话,您可能希望避免使用如此大的列表。

      总之,只要您事先知道要添加多少项目,或者可以估计(可能填充大一点),就可以随时使用容量。

      【讨论】:

        【解决方案5】:

        首先,为什么会出现异常:

        通过定义初始capacity,您只需指定列表可以存储的元素数量在需要调整大小之前。

        这并不意味着您的列表中有可访问的索引。您的列表仍然是空的,所以当您这样做时:

        myList[0] = 0.0f;
        

        你得到一个例外,但如果你这样做:

        List<float> myList = new List<float>(50);
        myList.Add(0);
        myList[0] = 2.0f;
        

        现在您不会遇到异常,因为索引 0 处有一个元素。

        现在是您问题的第二部分,容量实际上是什么意思:

        参见:List&lt;T&gt;.Capacity 属性:

        Capacity 是 List 之前可以存储的元素个数 需要调整大小,而 Count 是元素的数量 实际上在列表中。

        Capacity 总是大于或等于 Count。如果计数超过 容量增加元素,容量增加 在复制旧数组之前自动重新分配内部数组 元素并添加新元素。

        【讨论】:

          猜你喜欢
          • 2023-03-23
          • 2013-09-13
          • 2017-04-19
          • 1970-01-01
          • 2014-03-08
          • 1970-01-01
          • 1970-01-01
          • 2013-06-24
          • 1970-01-01
          相关资源
          最近更新 更多