c# -什么是List<用于的初始容量

本文关键字:容量 用于 什么 List | 更新日期: 2023-09-27 18:18:56

我想做以下事情:

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

但是我得到错误:

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

显然我误解了List(或者其他集合)的初始容量的作用。谁能给我解释一下初始容量是什么?

c# -什么是List<用于的初始容量

列表在底层有一个数组。这减少了当你加到50个元素时内存重新分配的数量。这需要时间和内存,并给垃圾收集器一些事情要做。

这就是为什么List(T)。容量是个东西。

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

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

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

这并不意味着你的列表中有可访问的索引。您的列表仍然是空的,所以当您执行

时:
myList[0] = 0.0f;

你会得到一个异常,但是如果你这样做:

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

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

现在你的问题的第二部分,容量到底是什么意思:

参见:List<T>.Capacity property:

Capacity是List之前可以存储的元素数量需要调整大小,而Count是需要调整大小的元素的数量

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

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

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

因此,如果您从一开始就知道要向列表中添加多少项。然后,您可以使用具有所需初始容量的底层数组创建List。
这样,你的列表在运行时分配一个新的底层数组的机会就会更小。
你可以通过调用List(T)构造函数(int32)
来设置底层数组的初始长度。你可以通过调用List(T)来获取当前数组的长度。

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

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

总而言之,在事先知道要添加多少项或可以估计(可能填充大一点)的情况下使用capacity。

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

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

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

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

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

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