List <T> и ArrayList емкость по умолчанию

2

Я рассматривал реализации .NET Список и ArrayList с Reflector.

При просмотре Добавить (элемент T) я столкнулся с этим. EnsureCapacity (this._size + 1):

public void Add(T item)
{
    if (this._size == this._items.Length)
    {
       this.EnsureCapacity(this._size + 1);
    }
    this._items[this._size++] = item;
    this._version++;
}

Итак, EnsureCapacity выглядит следующим образом:

private void EnsureCapacity(int min)
{
    if (this._items.Length < min)
    {
        int num = (this._items.Length == 0) ? 4 : (this._items.Length * 2);
        if (num < min)
        {
            num = min;
        }
        this.Capacity = num;
    }
}

Почему внутренняя емкость по умолчанию равна 4, а затем увеличивается на кратные 2 (т.е.: double)?

  • 1
    Более четкое и точное описание того, что он делает, - это «удвоение» размера. Я не просто педант. Я думал, что вы на самом деле говорили что-то совсем другое, пока я не прочитал код.
  • 0
    Подобный вопрос был задан ранее. См. Stackoverflow.com/questions/1424826/why-is-vector-array-doubled
Теги:
performance
algorithm
memory-management

4 ответа

7
Лучший ответ

Что касается удвоения, когда требуется изменить размер, это происходит по следующей причине. Скажем, что вы хотите вставить n элементы в List. Мы изменим размер списка не более, чем на log n раз. Поэтому вставка n элементов в List будет иметь наихудший случай O(n), поэтому вставки будут постоянными в амортизированном времени. Кроме того, количество потерянного пространства ограничено выше на n. Любая стратегия постоянного пропорционального роста приведет к постоянному амортизированному времени ввода и линейному пространству потерь. Рост быстрее, чем постоянный пропорциональный рост, может привести к более быстрым вставкам, но за счет большего пространства впустую. Рост медленнее, чем постоянный пропорциональный рост, может привести к меньшему расходованию пространства, но за счет более медленных вставок.

По умолчанию емкость такова, что небольшая память теряется впустую (и нет никакого вреда при старте мала, поскольку стратегия удвоения-масштабирования хороша с точки зрения времени/пространства, как мы только что видели).

6

4 является хорошим дефолтом, поскольку в большинстве коллекций будет только несколько элементов. Приращение выполняется, чтобы гарантировать, что вы не выполняете выделение памяти при каждом добавлении элемента.

Посмотрите эту хорошую статью Джоэла на использование памяти и почему выделение двойного, что вам нужно, является хорошей идеей.

http://www.joelonsoftware.com/printerFriendly/articles/fog0000000319.html

Здесь соответствующая цитата:

Предположим, вы написали функцию smart strcat, которая автоматически перераспределяет целевой буфер. Должен ли он всегда перераспределять его до нужного размера? Мой учитель и наставник Стэн Эйзенстат предполагает, что при вызове realloc вы должны всегда удваивать размер ранее выделенной памяти. Это означает, что вам никогда не придется вызывать realloc больше, чем lg n раз, что имеет достойные характеристики производительности даже для огромных строк, и вы никогда не тратите больше 50% своей памяти.

В качестве альтернативы, список < > и dictionary < > теперь по умолчанию равен 10, но я бы сказал, что они имеют одинаковую природущую логику.

  • 0
    Это также определенное поведение для реализации Microsoft CLR. (Но, похоже, это не требование ECMA-364.)
  • 0
    @ chris.w.mclean: Удвоение емкости при необходимости изменения размера гарантирует, что вы не будете распределять память каждый раз, когда добавляете элемент, но это не единственная стратегия, которая гарантирует это. Например, я мог бы просто изменить размер, увеличив емкость на два каждый раз, когда необходимо изменить размер. Смысл этого комментария в том, что избегание перераспределения при каждом добавлении элемента не является единственной целью изменения размера путем удвоения.
Показать ещё 2 комментария
0

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

  • 1
    Базовый malloc, вероятно, тоже любит кратные 2, так что это не просто простота.
  • 0
    Я полагал, что это подразумевается, так как кратные числа 2 очень распространены в вычислениях.
0

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

Емкость удваивается для каждого увеличения распределения, гарантируя, что она может удерживать удвоенное количество элементов, уже находящихся в контейнере. Это аналогичный алгоритм для векторного контейнера С++.

  • 0
    Дело в том, что оно не растет «экспоненциально», а «умножается» (в два раза).
  • 2
    @opello: это определение экспоненциального роста. Постоянный пропорциональный темп роста.
Показать ещё 1 комментарий

Ещё вопросы

Сообщество Overcoder
Наверх
Меню