Пока писал диплом (использую вложенные List < List<...>> c кучей данных), понадобилось проверить, сколько элементов влезает в List
Далее я создал List < List< double>>: оказалось, что при максимальном количестве элементов внутреннего листа (всё те же 33554432) внешний лист успевает создать 3 внутренних - а потом происходит всё то же "System.OutOfMemoryException". То есть, в List< List
А потом я создал всё тот же List< List< double>>, но при этом заполнял внутренний лист только на четверть от максимально возможного (33554432/4). Казалось бы, очевидно, что внешних листов должно получиться 12 (3 * 4=12) - но нет! Внешних листов создаётся 16. То есть, получается уже 256 * 4 Мб. А если заполнять внутренние количеством элементов (33554432/64), то внешних листов получается аж 265 (хотя 3 * 64=192) - то есть, 1060 Мб.
Уже ничего не понимая, я создал List< List< List< double>>>: при всё тех же максимально 33554432 элементах во внутреннем листе удаётся создать ровно два с половиной промежуточных листа -- после этого всё падает со всё тем же Exception'ом. То есть, получается 256 * 2,5 Мб.
Можете объяснить, как вообще выделяется память под List? И почему система не может занять пару гигабайт оперативки (у меня 4 Гб, х64), потом перейти в файл подкачки - и только потом откинуться со словами "System.OutOfMemoryException"? Зачем надо объявлять, что памяти больше нет, хотя её еще полно?
P.S. Если долго объяснять, посоветуйте что-нибудь почитать по этому поводу, если эти вещи где-то объяснены. Да, я чайник, знаю.
Всем спасибо!
Ответ
Число максимальных эелементов в списке будет зависеть от многих параметров. Но я просто попытаюсь объяснить, как выделяется память под эти элементы внутри класса List
List
private const int _defaultCapacity = 4;
private T[] _items;
static T[] _emptyArray = new T[0];
// Constructs a List. The list is initially empty and has a capacity
// of zero. Upon adding the first element to the list the capacity is
// increased to 16, and then increased in multiples of two as required.
public List() {
_items = _emptyArray;
}
// Constructs a List with a given initial capacity. The list is
// initially empty, but will have room for the given number of elements
// before any reallocations are required.
public List(int capacity) {
if (capacity < 0)
ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.capacity,
ExceptionResource.ArgumentOutOfRange_SmallCapacity);
_items = new T[capacity];
}
Второй случай может произойти во время добавления в список новых элементов или при присвоении нового значение свойству Capacity. Рассмотрим случай с добавлением новых элементов. Из комментария над первым конструктором видно, что размер внутреннего массива увеличивается вдвое. Происходит это тогда, когда текущий массив уже полностью занят элементами. Соответствующий код:
// Gets and sets the capacity of this list. The capacity is the size of
// the internal array used to hold items. When set, the internal
// array of the list is reallocated to the given capacity.
public int Capacity {
get { return _items.Length; }
set {
if (value != _items.Length) {
if (value < _size) {
ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.value,
ExceptionResource.ArgumentOutOfRange_SmallCapacity);
}
if (value > 0) {
T[] newItems = new T[value];
if (_size > 0) {
Array.Copy(_items, 0, newItems, 0, _size);
}
_items = newItems;
}
else {
_items = _emptyArray;
}
}
}
}
// Ensures that the capacity of this list is at least the given minimum
// value. If the currect capacity of the list is less than min, the
// capacity is increased to twice the current capacity or to min,
// whichever is larger.
private void EnsureCapacity(int min) {
if (_items.Length < min) {
int newCapacity = _items.Length == 0 ? _defaultCapacity : _items.Length * 2;
if (newCapacity < min) newCapacity = min;
Capacity = newCapacity;
}
}
Метод EnsureCapacity вызывается внутри всех методов списка, добавляющих в него новые элементы (Add(), Insert() и т.д.).
Какой из этого всего можно сделать вывод? Во время добавления элементов в список наступают моменты, когда памяти под элементы выделено в 3 раза больше, чем требуется на самом деле (момент, когда создался новый, в 2 раза больший массив и в него копируются данные из старого массива).
Исходники смотрел здесь: .NET List sources
Комментариев нет:
Отправить комментарий