Обычный конструктор ArrayList
:
ArrayList<?> list = new ArrayList<>();
Но есть также перегруженный конструктор с параметром для его начальной емкости:
ArrayList<?> list = new ArrayList<>(20);
Почему полезно создать ArrayList
исходную емкость, когда мы можем добавлять ее, как пожелаем?
java
data-structures
arraylist
capacity
обкрадывать
источник
источник
Ответы:
Если вы заранее знаете, каким будет размер
ArrayList
, более эффективно указать начальную емкость. Если вы этого не сделаете, внутренний массив придется многократно перераспределять по мере роста списка.Чем больше итоговый список, тем больше времени вы экономите, избегая перераспределений.
Тем не менее, даже без предварительного выделения, вставка
n
элементов в концеArrayList
гарантированно займет общееO(n)
время. Другими словами, добавление элемента является амортизированной операцией с постоянным временем. Это достигается за счет того, что каждое перераспределение увеличивает размер массива экспоненциально, как правило, на коэффициент1.5
. При таком подходе общее количество операций может быть показано какO(n)
.источник
O(n log n)
будетlog n
работатьn
раз. Это грубая переоценка (хотя технически правильная с большим О, потому что это верхняя граница). Он копирует s + s * 1,5 + s * 1,5 ^ 2 + ... + s * 1,5 ^ m (так что всего s * 1,5 ^ m <n <s * 1,5 ^ (m + 1)) элементов. Я плохо разбираюсь в суммах, поэтому я не могу дать вам точную математику на макушке головы (для коэффициента изменения размера 2 это 2n, так что это может быть 1.5n, чтобы дать или взять небольшую константу), но это не так. не нужно слишком щуриться, чтобы увидеть, что эта сумма не более чем постоянный фактор, превышающий n. Таким образом, требуется O (k * n) копий, что, конечно, O (n).Потому
ArrayList
что это динамически изменяемая структура данных массива , что означает, что он реализован как массив с начальным (по умолчанию) фиксированным размером. Когда это заполнится, массив будет расширен до двойного размера. Эта операция является дорогостоящей, поэтому вы хотите как можно меньше.Итак, если вы знаете, что ваша верхняя граница равна 20 элементам, то создание массива с начальной длиной 20 лучше, чем использование значения по умолчанию, скажем, 15, а затем изменить его размер
15*2 = 30
и использовать только 20, тратя впустую циклы для расширения.PS - Как говорит AmitG, коэффициент расширения зависит от конкретной реализации (в данном случае
(oldCapacity * 3)/2 + 1
)источник
int newCapacity = (oldCapacity * 3)/2 + 1;
Размер по умолчанию Arraylist составляет 10 .
Таким образом, если вы собираетесь добавить 100 или более записей, вы можете увидеть издержки перераспределения памяти.
Поэтому, если у вас есть представление о количестве элементов, которые будут храниться в Arraylist, лучше создать Arraylist с таким размером, а не начинать с 10, а затем увеличивать его.
источник
private static final int DEFAULT_CAPACITY = 10
Я фактически написал сообщение в блоге на тему 2 месяца назад. Статья предназначена для C #,
List<T>
но JavaArrayList
имеет очень похожую реализацию. ТакArrayList
как реализован с использованием динамического массива, он увеличивается в размере по требованию. Поэтому причина для конструктора емкости - в целях оптимизации.Когда происходит одна из этих операций изменения размеров, ArrayList копирует содержимое массива в новый массив, который в два раза больше емкости старого. Эта операция выполняется за O (n) времени.
пример
Вот пример того, как
ArrayList
размер увеличится:Таким образом, список начинается с емкости
10
, при добавлении 11-го элемента он увеличивается50% + 1
до16
. На 17-м пунктеArrayList
снова увеличен до25
и так далее. Теперь рассмотрим пример, в котором мы создаем список, в котором желаемая емкость уже известна как1000000
. Создание конструктораArrayList
без размера вызоветArrayList.add
1000000
время, которое обычно занимает O (1) или O (n) при изменении размера.Сравните это, используя конструктор, а затем вызов,
ArrayList.add
который гарантированно будет выполняться в O (1) .Java против C #
Java как и выше, начиная с
10
каждого размера и увеличивая его50% + 1
. C # начинается4
и увеличивается гораздо агрессивнее, удваивается при каждом изменении размера. Добавленный1000000
пример сверху для C # использует3097084
операции.Ссылки
источник
Установка начального размера ArrayList, например
ArrayList<>(100)
, уменьшает количество раз, когда должно происходить перераспределение внутренней памяти.Пример:
Как вы видите в приведенном выше примере - an
ArrayList
может быть расширен при необходимости. Это не показывает, что размер Arraylist обычно удваивается (хотя обратите внимание, что новый размер зависит от вашей реализации). Следующее цитата из Oracle :Очевидно, что если вы не знаете, какой диапазон вы будете удерживать, установка размера, вероятно, не будет хорошей идеей - однако, если у вас есть определенный диапазон, установка начальной емкости увеличит эффективность памяти ,
источник
ArrayList может содержать много значений, и при выполнении больших начальных вставок вы можете указать ArrayList выделять больший объем памяти для начала, чтобы не тратить циклы ЦП, когда он пытается выделить больше места для следующего элемента. Таким образом, выделить немного места в начале более эффективно.
источник
Это позволяет избежать возможных усилий по перераспределению для каждого отдельного объекта.
внутренне
new Object[]
создан.JVM требует усилий для создания,
new Object[]
когда вы добавляете элемент в массив. Если у вас нет кода выше (любой алго вам кажется) для перераспределения затем каждый раз , когда вы вызываете ,arraylist.add()
тоnew Object[]
должен быть создан , который не имеет смысла , и мы теряем время для увеличения размера на 1 для каждого объекта , которые будут добавлены. Так что лучше увеличить размерObject[]
с помощью следующей формулы.(JSL использовала формулу прогнозирования, приведенную ниже для динамически растущего массива, вместо того, чтобы каждый раз увеличиваться на 1. Потому что для роста требуется усилие JVM)
источник
add
- он уже использует некоторую формулу роста внутри страны. Следовательно, на вопрос нет ответа.int newCapacity = (oldCapacity * 3)/2 + 1;
который присутствует в классе ArrayList. Вы все еще думаете, что это без ответа?ArrayList
в амортизированном перераспределении происходит в любом случае с любым значением для первоначальной емкости. И вопрос такой: зачем вообще использовать нестандартное значение для начальной емкости? Помимо этого: «чтение между строк» не является чем-то желательным в техническом ответе. ;-)Я думаю, что каждый ArrayList создан со значением емкости инициализации "10". Так или иначе, если вы создадите ArrayList без установки емкости в конструкторе, он будет создан со значением по умолчанию.
источник
Я бы сказал, что это оптимизация. ArrayList без начальной емкости будет иметь ~ 10 пустых строк и будет расширяться при добавлении.
Чтобы получить список с точным количеством элементов, вам нужно вызвать trimToSize ()
источник
Согласно моему опыту
ArrayList
, предоставление начальной емкости - хороший способ избежать затрат на перераспределение. Но это несет оговорку. Все предложения, упомянутые выше, говорят о том, что исходную емкость следует указывать только тогда, когда известна приблизительная оценка количества элементов. Но когда мы пытаемся дать начальную емкость без какой-либо идеи, объем зарезервированной и неиспользованной памяти будет пустой тратой, поскольку она может никогда не потребоваться после заполнения списка требуемым количеством элементов. Я хочу сказать, что в начале мы можем прагматично распределять емкость, а затем находить разумный способ узнать требуемую минимальную емкость во время выполнения. ArrayList предоставляет метод с именемensureCapacity(int minCapacity)
. Но тогда нужно найти умный способ ...источник
Я протестировал ArrayList с и без initialCapacity и получил удивительный результат
Когда я установил для LOOP_NUMBER значение 100 000 или меньше, результатом является то, что установка initialCapacity эффективна.
Но когда я установил LOOP_NUMBER в 1,000,000, результат изменится на:
Наконец, я не мог понять, как это работает ?!
Образец кода:
Я проверил на Windows8.1 и JDK1.7.0_80
источник