C# - Для чего используется начальная емкость List‹T›?

Я пытаюсь сделать следующее:

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

Но я получаю сообщение об ошибке:

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

Очевидно, я неправильно понял, что делает начальная емкость списка (или, возможно, любой другой коллекции?). Может кто-нибудь объяснить мне, для чего нужна начальная емкость?


person Vesuvian    schedule 21.08.2014    source источник
comment
Прочтите о List<T>.Capacity.   -  person Habib    schedule 21.08.2014
comment
@rageit размер по умолчанию равен 0. Я думаю, что емкость по умолчанию составляет что-то вроде 16. (Исходя из памяти.)   -  person Timothy Shields    schedule 21.08.2014
comment
Что такое output? Вы определили myList размером 50.   -  person John Koerner    schedule 21.08.2014
comment
@TimothyShields Спасибо за исправление. Емкость по умолчанию для большинства фреймворков равна 0 - stackoverflow.com /questions/1762817/емкость-по-умолчанию-из-списка   -  person rageit    schedule 21.08.2014
comment
@rageit ArgumentOutOfRangeException выбрасывается, потому что ничего не было добавлено и делается попытка получить доступ к чему-то из индекса 0. Емкость не имеет к этому никакого отношения.   -  person Trevor Tubbs    schedule 21.08.2014
comment
Вам также следует прочитать эту статью., который оценивает разницу в производительности при предварительном размещении объектов словаря и без него.   -  person    schedule 05.07.2018


Ответы (5)


Список имеет массив под капотом. Это уменьшает объем перераспределения памяти, когда вы добавляете до 50 элементов. Это требует времени и памяти и дает работу сборщику мусора.

Вот почему List(T).Capacity это вещь.

Вот несколько тестов для 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, если вы знаете, насколько большим будет ваш список, действительно стоит того.

person Nathan Cooper    schedule 21.08.2014
comment
+1 Хороший пример с тестами - person rageit; 21.08.2014
comment
Спасибо за краткое объяснение! - person Vesuvian; 21.08.2014

Список является динамическим. Он может динамически добавлять и удалять элементы во время выполнения.
Реализация List использует базовый массив для хранения элементов списка. Длина базового массива называется Емкостью списка.
Каждый элемент списка хранится в базовом массиве.
При попытке добавить в список новый элемент Если в базовом массиве больше нет пустых мест, то будет создан новый массив большего размера.
Все элементы старого массива будут перемещены в новый массив, который также содержит больше места для новых элементов.< br> Затем новый массив будет установлен в качестве базового массива списка (старый будет удален).

Эта операция выделения нового массива и последующего перемещения элементов из старого массива в новый является дорогостоящей (с точки зрения производительности).

Поэтому, если вы с самого начала знаете, сколько элементов вы собираетесь добавить в список. Тогда вы можете создать Список с базовым массивом с необходимой вам начальной емкостью.
И так будет меньше шансов, что ваш list будет выделять новые базовые массивы во время выполнения.
Вы можете установить начальную длину базового массива, вызвав List(T) Constructor(int32)
Вы можете получить текущую длину массива, вызвав метод Свойство List(T).Capacity

person m1o2    schedule 21.08.2014

Список по-прежнему пуст, в нем нет элементов, но внутри зарезервирована память на 50 элементов. Это оптимизация, потому что когда вы добавляете элементы в список, он должен выделять новый массив вдвое большего размера, а затем копировать элементы из старого массива в новый.

Обратите внимание, что во время этого перехода, например, при переходе от 256 элементов и добавлении еще одного, он имеет в общей сложности (256 + 512) 768 элементов памяти, выделенных на тот момент, когда он копируется в новый массив. По сути, утроить предыдущую емкость. В зависимости от размера массива это может привести к исключению нехватки памяти. Поэтому, если вы знали заранее, что добавите в список только 257 элементов, вы можете заранее использовать емкость 257. Это позволит избежать тройного использования памяти, поскольку размер массива не должен увеличиваться, поскольку он уже достаточно велик. Обратите внимание, что вероятность возникновения исключений нехватки памяти увеличивается из-за того, что очень большие массивы размещаются в куче, которая не уплотняется, и, таким образом, фрагментация может привести к ситуациям, когда трудно найти непрерывный блок памяти для всего массива. . Поэтому иногда эта проблема приводит к возникновению исключений из-за нехватки памяти, когда кажется, что у вас много свободной памяти. Конечно, вы, вероятно, захотите избежать таких больших списков, если сможете.

Таким образом, используйте емкость в любое время, когда вы заранее знаете, сколько элементов вы будете добавлять, или можете оценить (возможно, немного больше).

person AaronLS    schedule 21.08.2014

Во-первых, почему вы получаете исключение:

Определив начальную емкость, вы просто указываете количество элементов, которые список может хранить до того, как потребуется изменить размер.

Это не означает, что у вас есть доступный индекс в вашем списке. Ваш список все еще пуст, поэтому, когда вы делаете:

myList[0] = 0.0f;

Вы получаете исключение, но если вы это сделаете:

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

Теперь вы не получите исключение, потому что есть элемент с индексом 0.

Теперь вторая часть вашего вопроса, что на самом деле означает емкость:

См.: свойство List<T>.Capacity:

Емкость — это количество элементов, которые Список может хранить до того, как потребуется изменение размера, тогда как Количество — это количество элементов, которые фактически находятся в Списке.

Емкость всегда больше или равна Count. Если Count превышает емкость при добавлении элементов, емкость увеличивается за счет автоматического перераспределения внутреннего массива перед копированием старых элементов и добавлением новых элементов.

person Habib    schedule 21.08.2014

Он используется для предварительного выделения внутреннего массива, используемого списком. (Размер этого внутреннего массива определяется как List.Capacity.)

Однако тот факт, что этот внутренний массив имеет определенный размер, не означает, что эти элементы списка доступны; это не так, пока вы (например) не используете List.Add() для добавления соответствующих элементов. Текущий адресуемый размер списка определяется как List.Count.

Когда вы добавляете новый элемент в список, он сначала проверяет, достаточно ли места во внутреннем массиве, и если есть, просто увеличивает внутренний счетчик и использует слот из внутреннего массива.

Если места недостаточно, он выделяет новый буфер в два раза больше старого, копирует все элементы из старого буфера в новый, а затем отбрасывает старый буфер. Затем он может использовать слот из нового буфера. Очевидно, что это довольно дорогая операция.

Установив начальный размер, вы можете избежать перераспределения некоторых (или всех) буферов.

Обычно используется, когда вы знаете максимально возможный размер списка, но не минимальный размер, и вы хотите заполнить список, выполняя как можно меньше перераспределений.

person Matthew Watson    schedule 21.08.2014