Почему в Java 8 емкость ArrayList по умолчанию равна нулю?

94

Насколько я помню, до Java 8 емкость по умолчанию ArrayListбыла 10.

Удивительно, но комментарий к конструктору по умолчанию (void) по-прежнему говорит: Constructs an empty list with an initial capacity of ten.

Откуда ArrayList.java:

/**
 * Shared empty array instance used for default sized empty instances. We
 * distinguish this from EMPTY_ELEMENTDATA to know how much to inflate when
 * first element is added.
 */
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

...

/**
 * Constructs an empty list with an initial capacity of ten.
 */
public ArrayList() {
    this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
Kevinarpe
источник

Ответы:

106

Технически это 10не ноль, если допустить ленивую инициализацию резервного массива. Увидеть:

public boolean add(E e) {
    ensureCapacityInternal(size + 1);
    elementData[size++] = e;
    return true;
}

private void ensureCapacityInternal(int minCapacity) {
    if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
        minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
    }

    ensureExplicitCapacity(minCapacity);
}

где

/**
 * Default initial capacity.
 */
private static final int DEFAULT_CAPACITY = 10;

Вы имеете в виду только начальный объект массива нулевого размера, который используется совместно всеми изначально пустыми ArrayListобъектами. Т.е. производительность 10гарантируется лениво , оптимизация присутствует и в Java 7.

По общему признанию, контракт конструктора не совсем точен. Возможно, в этом и есть источник путаницы.

Задний план

Вот электронное письмо Майка Дуигу

Я разместил обновленную версию пустого патча ArrayList и HashMap.

http://cr.openjdk.java.net/~mduigou/JDK-7143928/1/webrev/

Эта переработанная реализация не вводит новых полей ни в один из классов. Для ArrayList ленивое выделение резервного массива происходит только в том случае, если список создается с размером по умолчанию. По данным нашей группы анализа производительности, примерно 85% экземпляров ArrayList создаются с размером по умолчанию, поэтому такая оптимизация будет действительна в подавляющем большинстве случаев.

Для HashMap творчески используется поле порога для отслеживания запрошенного начального размера до тех пор, пока не понадобится массив сегментов. На стороне чтения пустая карта проверяется с помощью isEmpty (). По размеру записи сравнение (table == EMPTY_TABLE) используется для определения необходимости раздувания массива корзин. В readObject есть еще немного работы, чтобы попытаться выбрать эффективную начальную емкость.

От: http://mail.openjdk.java.net/pipermail/core-libs-dev/2013-April/015585.html

Лукас Эдер
источник
4
Согласно bugs.java.com/bugdatabase/view_bug.do?bug_id=7143928, это приводит к сокращению использования кучи и улучшенному времени отклика (показаны числа для двух приложений)
Томас Клегер
3
@khelwood: ArrayList на самом деле не «сообщает» о своей емкости, кроме как через этот Javadoc: нет getCapacity()метода или чего-то подобного. (Тем не менее, что-то вроде ensureCapacity(7)запрета для инициализированного по умолчанию ArrayList, поэтому я предполагаю, что мы действительно должны действовать так, как если бы его первоначальная емкость была действительно 10 ...)
ruakh
11
Хорошее копание. Начальная емкость по умолчанию действительно не нулевая, а 10, при этом случай по умолчанию выделяется лениво как особый случай. Вы можете наблюдать это, если многократно добавляете элементы в ArrayListсозданный конструктором без аргументов, а не передаете конструктору ноль int, и если вы смотрите на размер внутреннего массива рефлексивно или в отладчике. В случае по умолчанию длина массива изменяется от 0 до 10, затем до 15, 22, следуя скорости роста в 1,5 раза. Нулевое значение начальной емкости приводит к росту с 0 до 1, 2, 3, 4, 6, 9, 13, 19 ....
Стюарт Маркс
14
Я Майк Дуигу, автор изменения и цитируемого письма, и я одобряю это сообщение. 🙂 Как говорит Стюарт, мотивация заключалась в первую очередь в экономии места, а не в производительности, хотя есть также небольшой выигрыш в производительности из-за частого отказа от создания массива поддержки.
Майк Дуигу,
4
@assylias:; ^) нет, он все еще имеет свое место, поскольку синглтон по- emptyList()прежнему потребляет меньше памяти, чем несколько пустых ArrayListэкземпляров. Сейчас это менее важно и, следовательно, не нужно везде , особенно в местах с более высокой вероятностью добавления элементов в более позднее время. Также имейте в виду, что иногда вам нужен неизменяемый пустой список, и тогда это лучший вариант emptyList().
Хольгер
24

В java 8 емкость ArrayList по умолчанию равна 0, пока мы не добавим хотя бы один объект в объект ArrayList (вы можете назвать это ленивой инициализацией).

Теперь вопрос в том, почему это изменение было сделано в JAVA 8?

Ответ - для экономии потребления памяти. Миллионы объектов списков массивов создаются в Java-приложениях в реальном времени. Размер по умолчанию 10 объектов означает, что мы выделяем 10 указателей (40 или 80 байтов) для базового массива при создании и заполняем их нулями. Пустой массив (заполненный нулями) занимает много памяти.

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

Пожалуйста, смотрите код ниже для получения помощи.

ArrayList al = new ArrayList();          //Size:  0, Capacity:  0
ArrayList al = new ArrayList(5);         //Size:  0, Capacity:  5
ArrayList al = new ArrayList(new ArrayList(5)); //Size:  0, Capacity:  0
al.add( "shailesh" );                    //Size:  1, Capacity: 10

public static void main( String[] args )
        throws Exception
    {
        ArrayList al = new ArrayList();
        getCapacity( al );
        al.add( "shailesh" );
        getCapacity( al );
    }

    static void getCapacity( ArrayList<?> l )
        throws Exception
    {
        Field dataField = ArrayList.class.getDeclaredField( "elementData" );
        dataField.setAccessible( true );
        System.out.format( "Size: %2d, Capacity: %2d%n", l.size(), ( (Object[]) dataField.get( l ) ).length );
}

Response: - 
Size:  0, Capacity:  0
Size:  1, Capacity: 10

В статье « Емкость по умолчанию для ArrayList в Java 8» это подробно объясняется.

Шайлеш Викрам Сингх
источник
7

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

При этом в некоторых контекстах можно было бы дополнительно улучшить производительность, если бы была перегрузка addAll, в которой указывалось, сколько элементов (если есть), вероятно, будет добавлено в список после текущего, а какие могут используйте это, чтобы повлиять на его поведение распределения. В некоторых случаях код, который добавляет последние несколько элементов в список, будет иметь довольно хорошее представление о том, что списку никогда не понадобится дополнительное пространство. Есть много ситуаций, когда список заполняется один раз и больше не изменяется. Если в точке кода известно, что конечный размер списка будет 170 элементов, у него будет 150 элементов и резервное хранилище размером 160,

суперкар
источник
Очень хорошие отзывы о addAll(). Это еще одна возможность повысить эффективность работы с первым malloc.
kevinarpe
@kevinarpe: Мне бы хотелось, чтобы в библиотеке Java было разработано еще несколько способов, позволяющих программам указывать, как вещи, вероятно, будут использоваться. Например, старый стиль подстроки был плохим для некоторых случаев использования, но превосходным для других. Если бы были отдельные функции для «подстроки, которая, вероятно, переживет оригинал» и «подстроки, которая вряд ли переживет оригинал», и код использовал бы правильную в 90% случаев, я бы подумал, что они могли бы значительно превзойти по производительности либо реализация старой или новой строки.
supercat
3

Вопрос в том «почему?».

Проверки профилирования памяти (например ( https://www.yourkit.com/docs/java/help/inspections_mem.jsp#sparse_arrays ) показывают, что пустые (заполненные нулями) массивы занимают тонны памяти.

Размер по умолчанию 10 объектов означает, что мы выделяем 10 указателей (40 или 80 байтов) для базового массива при создании и заполняем их нулями. Настоящие java-приложения создают миллионы списков массивов.

Введенная модификация удаляет ^ W, откладывает это потребление памяти до того момента, когда вы действительно будете использовать список массивов.

ya_pulser
источник
Пожалуйста, исправьте «потреблять» на «отходы». Ссылка, которую вы предоставляете, не означает, что они начинают поглощать память повсюду, просто массивы с нулевыми элементами непропорционально расходуют память, выделенную для них. «Потребление» подразумевает, что они волшебным образом используют память, превышающую выделенную, а это не так.
mechalynx
1

После вышеупомянутого вопроса я просмотрел документ ArrayList для Java 8. Я обнаружил, что размер по умолчанию по-прежнему равен 10.

См. Ниже

Рахул Маурья
источник
0

Размер ArrayList по умолчанию в JAVA 8 равен 10. Единственное изменение, внесенное в JAVA 8, состоит в том, что если кодировщик добавляет элементов меньше 10, то оставшиеся пустые места в arrayylist не задаются равными нулю. Сказав так, потому что я сам прошел через эту ситуацию, и затмение заставило меня взглянуть на это изменение JAVA 8.

Вы можете обосновать это изменение, посмотрев на скриншот ниже. В нем вы можете видеть, что размер ArrayList указан как 10 в Object [10], но количество отображаемых элементов составляет всего 7. Остальные элементы с нулевым значением здесь не отображаются. В JAVA 7 ниже снимок экрана такой же, с одним изменением, которое заключается в том, что также отображаются элементы нулевого значения, для которых кодировщику необходимо написать код для обработки нулевых значений, если он выполняет итерацию полного списка массивов, в то время как в JAVA 8 это бремя снимается с руководитель кодера / разработчика.

Ссылка на скриншот.

TechTeddy
источник