Чому в Java 8 зараз нульова ємність ArrayList дорівнює нулю?


93

Наскільки я пам'ятаю, до Java 8 ємність за замовчуванням ArrayListстановила 10.

Дивно, але коментар до конструктора за замовчуванням (недійсний) все ще говорить: 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;
}

Відповіді:


105

Технічно це 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 це призводить до скорочення використання купи та покращення часу реагування (показано номери для двох додатків)
Thomas Kläger

3
@khelwood: ArrayList насправді не "повідомляє" про свою потужність, окрім як через цей Javadoc: немає getCapacity()методу чи подібного. (Ось сказано, щось подібне ensureCapacity(7)є неоперативним для ініціалізованого ArrayList за замовчуванням, тому я думаю, що ми справді повинні діяти так, ніби його початкова потужність була справді 10.).
ruakh

10
Гарне копання. Початкова потужність за замовчуванням справді не дорівнює нулю, але 10, при цьому за замовчуванням лінь виділяється як особливий випадок. Ви можете це спостерігати, якщо ви неодноразово додаєте елементи до ArrayListствореного конструктором no-arg vs, передаючи нулю intконструктору, і якщо ви дивитесь на внутрішній розмір масиву рефлекторно або в налагоджувальній машині. У випадку за замовчуванням масив стрибає з довжини 0 до 10, потім до 15, 22, після темпу зростання в 1,5 рази. Перехід нуля як початкової потужності призводить до зростання від 0 до 1, 2, 3, 4, 6, 9, 13, 19 ....
Стюарт Маркс

13
Я Майк Дуйгу, автор змін та цитованого електронного листа, і я схвалюю це повідомлення. Як стверджує Стюарт, мотивація полягала в першу чергу про економію місця, а не про продуктивність, хоча також є невелика користь від продуктивності, що часто уникає створення резервного масиву.
Майк Дуйгу

4
@assylias:; ^) ні, він все ще має своє місце, оскільки сингл emptyList()все ще споживає менше пам'яті, ніж кілька порожніх ArrayListекземплярів. Це просто менш важливо зараз і, отже, не потрібно в кожному місці, особливо не в місцях з більшою ймовірністю додавання елементів у більш пізній час. Також пам’ятайте, що вам іноді потрібно непорушний порожній список, і тоді emptyList()це шлях.
Холгер

23

У 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(). Це ще одна можливість підвищити ефективність у перших жеребках.
кевінарпе

@kevinarpe: Я б хотів, щоб бібліотека Java розробляла ще кілька способів, щоб програми вказували, як все можна використовувати. Наприклад, старий стиль підрядок, для одних випадків використання, був хижим, але для інших відмінним. Якби були окремі функції для "підрядка, яка, можливо, переживе оригінал" та "підрядка, яка навряд чи переживе оригінал", і код використовував правильний 90% часу, я б вважав, що вони могли б значно перевищити або стара або нова рядова реалізація.
supercat

3

Питання "чому?".

Перевірка профілювання пам'яті (наприклад ( https://www.yourkit.com/docs/java/help/inspections_mem.jsp#sparse_arrays ) показує, що порожні (заповнені нулями) масиви займають тонни пам'яті.

Типовий розмір 10 об'єктів означає, що ми виділяємо 10 покажчиків (40 або 80 байт) для базового масиву при створенні і заповнюємо їх нулями. Справжні програми Java створюють мільйони списків масивів.

Введена модифікація видаляє ^ W відкласти це споживання пам'яті до моменту, коли ви фактично будете використовувати список масиву.


Будь ласка, виправте "споживайте" разом із "відходами". Посилання, яке ви надаєте, не означає, що вони починають поширювати пам’ять скрізь, лише те, що масиви з нульовими елементами непропорційно витрачають виділену для них пам'ять. "Споживайте" означає, що вони магічно використовують пам'ять поза їх розподілом, що не так.
mechalynx

0

Розмір за замовчуванням ArrayList у JAVA 8 - це стиль 10. Єдина зміна, внесена в JAVA 8, полягає в тому, що якщо кодер додає елементи менше 10, то решта порожніх місць масиву архівників не задаються нульовими. Висловивши це тому, що я сам пережив цю ситуацію, і затемнення змусило мене заглянути в цю зміну JAVA 8.

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

Екран посилання постріл.


0

Після вищезазначеного питання я переглянув ArrayList Document of Java 8. Я виявив, що розмір за замовчуванням все ще 10.

Дивіться нижче

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.