Зачем начинать ArrayList с начальной пропускной способностью?

Обычный конструктор ArrayList:

ArrayList<?> list = new ArrayList<>();

Но есть также перегруженный конструктор с параметром для его начальной емкости:

ArrayList<?> list = new ArrayList<>(20);

Почему полезно создавать ArrayList с начальной емкостью, когда мы можем добавить к нему, как нам нравится?

Ответ 1

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

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

Тем не менее, без предварительного выделения, включение элементов n в конце ArrayList гарантированно будет иметь общее время O(n). Другими словами, добавление элемента представляет собой амортизированную операцию с постоянным временем. Это достигается за счет того, что каждое перераспределение увеличивает размер массива экспоненциально, как правило, в <<24 > . При таком подходе общее число операций может быть показано как O(n).

Ответ 2

Потому что ArrayList представляет собой структуру данных динамическое изменение размерa > , что означает, что она реализована как массив с исходным (по умолчанию) фиксированным размер. Когда это заполняется, массив будет расширен до двух размеров. Эта операция является дорогостоящей, поэтому вы хотите как можно меньше.

Итак, если вы знаете, что ваша верхняя граница - 20 элементов, то создание массива с начальной длиной 20 лучше, чем использование значения по умолчанию, скажем, 15, а затем изменить его размер на 15*2 = 30 и использовать только 20, потратив циклов для разложения.

P.S. - Как говорит AmitG, коэффициент расширения специфичен для реализации (в данном случае (oldCapacity * 3)/2 + 1)

Ответ 3

Размер по умолчанию для Arraylist 10.

    /**
     * Constructs an empty list with an initial capacity of ten.
     */
    public ArrayList() {
    this(10);
    } 

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

ArrayList<?> list = new ArrayList<>();    
// same as  new ArrayList<>(10);      

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

Ответ 4

На самом деле я написал сообщение в блоге по теме 2 месяца назад. Статья предназначена для С# List<T>, но Java ArrayList имеет очень похожую реализацию. Поскольку ArrayList реализуется с использованием динамического массива, он увеличивается по размеру по требованию. Поэтому причина для конструктора емкости для оптимизации.

Когда происходит одна из этих операций с изменением размера, ArrayList копирует содержимое массива в новый массив, который в два раза превышает емкость старого. Эта операция выполняется в O (n) времени.

Пример

Вот пример того, как ArrayList будет увеличиваться в размере:

10
16
25
38
58
... 17 resizes ...
198578
297868
446803
670205
1005308

Таким образом, список начинается с емкости 10, когда добавляется 11-й элемент, он увеличивается на 50% + 1 до 16. На 17-м элементе ArrayList снова увеличивается до 25 и так далее. Теперь рассмотрим пример, в котором мы создаем список, где желаемая емкость уже известна как 1000000. Создание ArrayList без конструктора size вызовет ArrayList.add 1000000 раз, который обычно принимает O (1) или O (n) при изменении размера.

1000000 + 16 + 25 +... + 670205 + 1005308 = 4015851 операции

Сравните это с помощью конструктора, а затем вызовите ArrayList.add, который гарантированно будет работать в O (1).

1000000 + 1000000 = 2000000 операций

Java vs С#

Java как указано выше, начиная с 10 и увеличивая каждый размер в 50% + 1. С# начинается с 4 и увеличивается гораздо более агрессивно, удваиваясь при каждом изменении размера. 1000000 добавляет пример сверху для С# использует операции 3097084.

Ссылки

Ответ 5

Установка начального размера ArrayList, например. до ArrayList<>(100), уменьшает количество перераспределений внутренней памяти.

Пример:

ArrayList example = new ArrayList<Integer>(3);
example.add(1); // size() == 1
example.add(2); // size() == 2, 
example.add(2); // size() == 3, example has been 'filled'
example.add(3); // size() == 4, example has been 'expanded' so that the fourth element can be added. 

Как вы видите в приведенном выше примере - ArrayList можно расширить, если это необходимо. Это не показывает, что размер Arraylist обычно удваивается (хотя обратите внимание, что новый размер зависит от вашей реализации). Ниже приведено Oracle:

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

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

Ответ 6

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

int newCapacity = (oldCapacity * 3)/2 + 1;

internal new Object[].
JVM требует усилий для создания new Object[] при добавлении элемента в arraylist. Если у вас нет над кодом (любой алго, который вы думаете) для перераспределения, то каждый раз при вызове arraylist.add() необходимо создать new Object[], что бессмысленно, и мы теряем время для увеличения размера на 1 для каждого объекты, которые необходимо добавить. Поэтому лучше увеличить размер Object[] следующей формулой.
(JSL использовал формулу forcasting, приведенную ниже, для динамически растущего arraylist вместо того, чтобы расти на 1. Каждый раз, потому что для роста это требует усилий JVM)

int newCapacity = (oldCapacity * 3)/2 + 1;

Ответ 7

Я думаю, каждый массив ArrayList создается с значением емкости инициализации "10". Так или иначе, если вы создадите ArrayList без установки емкости внутри конструктора, он будет создан со значением по умолчанию.

Ответ 8

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

Ответ 9

Я бы сказал, что это оптимизация. ArrayList без начальной емкости будет иметь ~ 10 пустых строк и будет расширяться при добавлении.

Чтобы иметь список с точно количеством элементов, которые нужно вызвать trimToSize()

Ответ 10

Согласно моему опыту с ArrayList, предоставление первоначальной емкости - хороший способ избежать затрат на перераспределение. Но он имеет оговорку. Все упомянутые выше предложения говорят о том, что необходимо предоставить начальную емкость только тогда, когда известна приблизительная оценка количества элементов. Но когда мы пытаемся дать начальную емкость без какой-либо идеи, объем памяти, зарезервированной и неиспользованной, будет пустой тратой, поскольку она никогда не понадобится после заполнения списка до необходимого количества элементов. То, что я говорю, мы можем быть прагматичными вначале при распределении пропускной способности, а затем найти умный способ узнать требуемую минимальную емкость во время выполнения. ArrayList предоставляет метод под названием ensureCapacity(int minCapacity). Но тогда найдется умный способ...

Ответ 11

Я тестировал ArrayList с и без initialCapacity, и я получил отличный результат
Когда я устанавливаю LOOP_NUMBER до 100 000 или менее, результатом является то, что параметр initialCapacity эффективен.

list1Sttop-list1Start = 14
list2Sttop-list2Start = 10


Но когда я установил LOOP_NUMBER на 1,000,000, результат изменится на:

list1Stop-list1Start = 40
list2Stop-list2Start = 66


Наконец, я не мог понять, как это работает?!
Пример кода:

 public static final int LOOP_NUMBER = 100000;

public static void main(String[] args) {

    long list1Start = System.currentTimeMillis();
    List<Integer> list1 = new ArrayList();
    for (int i = 0; i < LOOP_NUMBER; i++) {
        list1.add(i);
    }
    long list1Stop = System.currentTimeMillis();
    System.out.println("list1Stop-list1Start = " + String.valueOf(list1Stop - list1Start));

    long list2Start = System.currentTimeMillis();
    List<Integer> list2 = new ArrayList(LOOP_NUMBER);
    for (int i = 0; i < LOOP_NUMBER; i++) {
        list2.add(i);
    }
    long list2Stop = System.currentTimeMillis();
    System.out.println("list2Stop-list2Start = " + String.valueOf(list2Stop - list2Start));
}

Я тестировал на windows8.1 и jdk1.7.0_80