2009-09-15 3 views
15

Почему классическая реализация Vector (ArrayList для Java-людей) удваивает размер внутреннего массива на каждом расширении вместо того, чтобы увеличивать или увеличивать в четыре раза?Почему векторный массив удваивается?

+0

Можно также задаться вопросом, почему не умножить его на 1.5? Или 1.8 и т. Д.? (Можно умножить на 1,5, затем округлить до следующего наибольшего целого числа, скажем.) – Peter

+0

+1 Большой вопрос. –

ответ

19

При вычислении среднего времени для вставки в вектор необходимо учитывать не растущие вставки и растущие вставки.

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

Если вставить п пунктов, и вы растете на фактор по мере необходимости, то есть о общая = п + Σ я [0 < я < 1 + пер A n] операции. В худшем случае вы используете 1/A выделенного хранилища.

Наглядно А = 2 означает, что в худшем случае вы имеете уплотнительное общего = 2n, так уплотнительного среднего представляет собой О (1), а в худшем случае использование 50% выделенное хранение ,

Для большего , у вас есть нижняя уплотнительного общей, но больше впустую хранение.

Для меньшего , о общая больше, но вы не тратите так много места. Пока он растет геометрически, все равно O (1) амортизируется время вставки, но константа будет выше.

Для факторов роста 1,25 (красный), 1,5 (голубой), 2 (черный), 3 (синий) и 4 (зеленый), эти графики показывают эффективность точки и среднего размера (отношение размера/выделенного пространства; лучше) слева и время эффективность (соотношение вставки/операции, лучше) справа для вставки 400 000 предметов. 100% -ная эффективность пространства достигается для всех факторов роста непосредственно перед изменением размера; в случае A = 2 показывает эффективность времени между 25% и 50%, а также экономии пространства около 50%, что хорошо для большинства случаев:

space and time efficiency graph - C like implementations

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

space and time efficiency graph - Java like implementations

+1

Я подтвердил ваш ответ, но предложил бы изучить, сколько раз каждый элемент в коллекции будет перемещен, когда он набит только от уровня, необходимого для расширения. При коэффициенте роста k только 1/k предметов будут перемещаться даже один раз, 1/k^2 будет перемещаться как минимум дважды, 1/k^3 будет перемещаться три раза и т. Д., Поэтому среднее число раз каждый элемент данных будет перемещаться в 'n' расширениях будет 1/k + 1/k^2 + 1/k^3 + ... 1/k^n, который является ограниченным геометрическим рядом. – supercat

+0

Похоже, что изображения, которые были включены для этого ответа, теперь являются объявлениями для изображений. У вас их все еще есть? – CCovey

4

Экспоненциально удвоение размера массива (или строки) является хорошим компромиссом между наличием достаточного количества ячеек в массиве и тратой слишком много памяти.

Скажем, мы начинаем с 10 элементами:

1 - 10
2 - 20
3 - 40
4 - 80
5 - 160

Когда мы утроить размер, мы быстро развиваемся

1 - 10
2 - 30
- 90
4 - 270
5 - 810

На практике вы бы расти, может быть, 10 или 12 раз. Если вы утроите, вы, возможно, сделаете это 7 или 8 раз - время выполнения для перераспределения - это несколько раз достаточно мало, чтобы волноваться, но вы, скорее всего, полностью превысите необходимый размер.

+1

Хорошо, но тогда вы можете утверждать, что вектор может просто расшириться до еще одного элемента или расширить на половину количества элементов. Есть ли какая-то особая причина для его удвоения? – TheOne

+0

Если ваш текущий размер составляет 1 000 000 ячеек, то удвоение и копирование кажется очень дорогостоящим. – TheOne

+1

Когда вы удвоитесь, вы гарантированно потратите на ** большинство ** объем памяти, который вы хотите использовать. Точка роста экспоненциально не должна расти вообще, поскольку вы вокруг размера цели. –

2

Если вы спрашиваете о реализации Java Vector и ArrayList, то это не обязательно удваивается при каждом расширении.

Из Javadoc для Vector:

Каждый вектор пытается оптимизировать управление хранением данных, поддерживая capacity и capacityIncrement. Емкость всегда не меньше размера вектора; он обычно больше, поскольку, поскольку компоненты добавляются к вектору, хранилище вектора увеличивается в кусках размером capacityIncrement. Приложение может увеличить пропускную способность вектора перед вставкой большого количества компонентов; это уменьшает количество инкрементного перераспределения.

Один из конструкторов для Vector позволяет указать начальный размер и прирост емкости для вектора. Класс Vector также предоставляет ensureCapacity(int minCapacity) и setSize(int newSize) для ручной настройки минимального размера вектора и для изменения размера Vector самостоятельно.

Класс ArrayList очень похож:

Каждый ArrayList экземпляр имеет емкость. Емкость - это размер массива, используемого для хранения элементов в списке. Он всегда не меньше размера списка. Поскольку элементы добавляются в ArrayList, его емкость растет автоматически. Детали политики роста не указаны за пределами того факта, что добавление элемента имеет постоянную амортизированную временную стоимость.

Приложение может увеличить емкость экземпляра ArrayList перед добавлением большого количества элементов, используя операцию обеспечения работоспособности. Это может уменьшить количество инкрементного перераспределения.

Если вы спрашиваете об общей реализации вектора, чем выбор увеличения размера и того, насколько это компромисс. Как правило, векторы поддерживаются массивами. Массивы имеют фиксированный размер. Чтобы изменить размер вектора, поскольку он заполнен, вы должны скопировать все элементы массива в новый массив большего размера. Если вы сделаете свой новый массив слишком большим, то вы выделили память, которую вы никогда не будете использовать. Если он слишком мал, может потребоваться слишком много времени, чтобы скопировать элементы из старого массива в новый, более крупный массив - операцию, которую вы не хотите выполнять очень часто.

-1

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

3

Если вы должны были выделить блок памяти необычного размера, тогда, когда этот блок будет освобожден (либо потому, что вы измените его размер, либо получится GC'd), в памяти будет отверстие необычного размера, которое могло бы вызывают головные боли для менеджера памяти. Поэтому обычно предпочтительнее распределять память по двум. В некоторых случаях основной менеджер памяти будет давать вам только блоки определенных размеров, и если вы запросите странный размер, округлите его до следующего большего размера. Таким образом, вместо того, чтобы запрашивать 470 единиц, возвращая 512 в любом случае, а затем снова изменяя размер, как только вы используете все 470, о которых вы просили, возможно, просто попросите 512 начать с.

+0

Я не согласен с этим ответом. Я не уверен, что он отвечает «почему не на 3 или 4 или 5 темпа роста».Он отвечает на несколько иной вопрос (зачем распределять память на границах полномочий двух)? –

+1

Это, конечно, не та, которую я бы выбрал. Я думал об этом скорее как о дополнительном ответе. В дополнение к другим хорошо объясненным причинам роста скорости, вы тратите ресурсы, если новый массив не является силой двух. Поэтому, учитывая другие аргументы в пользу того, почему более крупный множитель не был бы хорош, единственная сила двух, которая подходит хорошо, равна 2. Она предполагает, что исходный размер также был мощью двух, конечно, но я думаю, что большинство векторов классы стараются это сделать. – kwatford

+0

Справа. «Не согласен», вероятно, было немного сильным :) Кроме того, вы определенно могли бы разработать алгоритм, который обеспечит вам примерно 1,5 рост, который все еще гарантирует, что вы выровнены по словам. Если массив байтов имеет длину 64 байта, вы можете определенно добавить к нему 32 байта и по-прежнему поддерживать выравнивание слов. –

2

Лично я считаю, что это был произвольный выбор. Мы могли бы использовать base e вместо основания 2 (вместо удвоения только одного размера по (1 + e).)

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

База 2 является компромиссом.

3

Любое многократное компромиссное решение. Сделайте его слишком большим, и вы потеряете слишком много памяти. Сделайте его слишком маленьким, и вы потратите много времени на перераспределение и копирование. Я думаю, что удвоение существует, потому что оно работает и очень легко реализовать. Я также видел запатентованную STL-подобную библиотеку, которая использует 1.5 как множитель для того же самого - я думаю, его разработчики считали, что удвоение тратит слишком много памяти.

Смежные вопросы