2

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

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

但我得到了错误:

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

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

4

5 回答 5

12

列表在引擎盖下有一个数组。当您添加多达 50 个元素时,这会减少内存重新分配的数量。这需要时间和内存,并让垃圾收集器有事情要做。

这就是为什么List(T).Capacity是一回事。

以下是100秒的一些基准.Add

 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 一个提示听起来是值得的。

于 2014-08-21T15:00:31.073 回答
5

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

这种分配新数组然后将项目从旧数组移动到新数组的操作是昂贵的(性能方面)。

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

于 2014-08-21T15:07:06.543 回答
2

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

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

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

于 2014-08-21T15:09:36.447 回答
2

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

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

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

myList[0] = 0.0f;

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

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

现在你不会得到异常,因为 index 有一个元素0

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

请参阅:List<T>.Capacity属性:

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

容量始终大于或等于 Count。如果在添加元素时 Count 超过了容量,则通过在复制旧元素和添加新元素之前自动重新分配内部数组来增加容量。

于 2014-08-21T15:10:14.807 回答
0

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

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

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

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

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

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

于 2014-08-21T15:03:35.587 回答