Масив або список на Java. Що швидше?


351

Мені доводиться зберігати тисячі рядків у пам'яті, щоб послідовно отримувати доступ до Java. Чи потрібно зберігати їх у масиві чи я повинен використовувати якийсь список?

Оскільки масиви зберігають усі дані у суміжній частині пам’яті (на відміну від списків), чи може використання масиву для зберігання тисяч рядків викликати проблеми?


5
"Оскільки масиви зберігають усі дані в безперервному шматку пам'яті", чи є у вас якесь цитування, щоб створити резервну копію для Java?
мат b

1
Без мату. Я знаю це для C. Я здогадуюсь, Java би використовував той самий метод.
euphoria83

Я сумніваюся, що це збереже їх у єдиному шматку пам’яті.
Fortyrunner

3
Навіть якщо це єдиний блок пам'яті, він все одно коштуватиме близько 1000 * 4 = 4 кбіт, що не багато пам'яті.
CookieOfFortune

3
@mattb Ось що означає "масив" у всій CS. Цитування не потрібне. Численні посилання JLS та [JVM Spec] () на довжину масиву зрозумілі лише тоді, коли масиви є суміжними.
Маркіз Лорн

Відповіді:


358

Я пропоную скористатися профілером для тестування, який швидше.

Моя особиста думка, що ви повинні використовувати Списки.

Я працюю над великою базою кодів, і попередня група розробників всюди використовувала масиви . Це зробило код дуже негнучким. Змінивши великі куски цього списку на Списки, ми не помітили різниці в швидкості.


2
@Fortyrunner - З вашого досвіду, чи є такий вибір на Java між абстракцією та необробленими формами даних, які дійсно суттєво різняться у продуктивності?
euphoria83

4
Одне з питань вимірювання продуктивності полягає в тому, що вам постійно доведеться повторно протестувати проти нових версій Java. Я працюю над проблемою в той момент, коли хтось використовував int в цілому для ключа в карті (щоб заощадити місце / час). Зараз нам потрібно змінити всі рядки на новий об’єкт - його болісно.
Fortyrunner

9
Отже .. Я зараз намагаюся триматися подалі від необроблених даних. Це рідко робить помітну різницю. Точка доступу - це дивовижна технологія, і ви ніколи не повинні намагатися вдруге здогадуватися. Просто спробуйте написати простий, бездоганний код і Hotspot зробить все інше.
Fortyrunner

4
Пам’ятайте, що результати профілерів дійсні лише для платформи Java, проти якої ви працюєте. Що може відрізнятися від ваших клієнтів.
Mikkel Løkke

4
Ефективна Java рекомендує списки, оскільки вони допомагають у взаємодії з API, а також більш безпечні при безпеці типу.
juanmf

164

Спосіб Java полягає в тому, що ви повинні врахувати, яка абстракція даних найбільше відповідає вашим потребам. Пам'ятайте, що в Java список є абстрактним, а не конкретним типом даних. Ви повинні оголосити рядки як Список, а потім ініціалізувати його за допомогою реалізації ArrayList.

List<String> strings = new ArrayList<String>();

Цей поділ абстрактного типу даних та конкретна реалізація є одним із ключових аспектів об'єктно-орієнтованого програмування.

ArrayList реалізує список абстрактних типів даних, використовуючи масив як основну реалізацію. Швидкість доступу практично ідентична масиву, з додатковими перевагами можливості додавання та віднімання елементів до списку (хоча це операція O (n) за допомогою ArrayList), і якщо ви вирішите змінити базову реалізацію пізніше ти можеш. Наприклад, якщо ви розумієте, що вам потрібен синхронізований доступ, ви можете змінити реалізацію на векторну, не переписуючи весь код.

Фактично, ArrayList був спеціально розроблений для заміни низькорівневої конструкції масиву у більшості контекстів. Якби Java розроблялася сьогодні, цілком можливо, що масиви взагалі були б залишені на користь конструкції ArrayList.

Оскільки масиви зберігають усі дані у суміжній частині пам’яті (на відміну від списків), чи може використання масиву для зберігання тисяч рядків викликати проблеми?

У Java всі колекції зберігають лише посилання на об'єкти, а не на самі об'єкти. І масиви, і ArrayList зберігатимуть кілька тисяч посилань у суміжному масиві, тому вони по суті ідентичні. Ви можете вважати, що суміжний блок з декількох тисяч 32-бітних посилань завжди буде доступний на сучасному обладнанні. Це не гарантує, що у вас взагалі не вистачить пам’яті, звичайно, лише те, що суміжний блок вимоги пам’яті не складно виконати.


Додавання, звичайно, може включати перерозподіл резервного масиву, тому, якщо продуктивність важлива, а розмір масиву відомий заздалегідь, варто розглянути можливість використання ArrayList # secureCapacity.
JesperE

6
Ви не оплачуєте тут вартість динамічної прив’язки?
Урі

2
Я б припустив, що додавання не є O (n) у ArrayList, при додаванні більше одного разу має бути деякий ефект аммортизації, наприклад, ємність збільшується вдвічі, а не збільшується лише на 1.
zedoo

@zedoo Я думаю, що вони означали додавання і віднімання в середині.
MalcolmOcean

"Якби Java розроблялася сьогодні, цілком можливо, що масиви взагалі були б залишені на користь конструкції ArrayList." ... Я серйозно сумніваюся, що це було б правдою. Якби це сьогодні було переписано JVM , то, що ви сказали, це, безумовно, можливо. Але у нас є JVM, масиви - це фундаментальний тип у Java.
scottb

100

Хоча відповіді, що пропонують використовувати ArrayList, мають сенс у більшості сценаріїв, насправді на питання щодо відносної продуктивності насправді не було дано відповідей.

З масивом можна виконати кілька речей:

  • створити його
  • встановити елемент
  • отримати предмет
  • клонувати / копіювати

Загальний висновок

Хоча операції отримання та встановлення дещо повільніші на ArrayList (відповідно 1 та 3 наносекунда за виклик на моїй машині), використання ArrayList проти масиву для будь-якого неінтенсивного використання дуже мало. Однак слід пам’ятати про кілька речей:

  • зміни розміру операцій зі списку (під час дзвінка list.add(...)) є дорогими, і потрібно намагатися встановити початкову потужність на достатньому рівні, коли це можливо (зауважте, що ця ж проблема виникає при використанні масиву)
  • при роботі з примітивами, масиви можуть бути значно швидшими, оскільки вони дозволять уникнути багатьох перетворень боксу / розпакування
  • додаток, який отримує / встановлює значення в ArrayList (не дуже часто!), може побачити збільшення продуктивності більше 25%, перейшовши на масив

Детальні результати

Ось результати, які я виміряв для цих трьох операцій, використовуючи бібліотеку jmh бенчмаркінгу (разів у наносекундах) з JDK 7 на стандартній настільній машині x86. Зауважте, що тести ArrayList ніколи не змінюються, щоб переконатися, що результати порівнянні. Код орієнтиру доступний тут .

Створення масиву / ArrayList

Я провів 4 тести, виконавши такі твердження:

  • createArray1: Integer[] array = new Integer[1];
  • createList1: List<Integer> list = new ArrayList<> (1);
  • createArray10000: Integer[] array = new Integer[10000];
  • createList10000: List<Integer> list = new ArrayList<> (10000);

Результати (у наносекундах за дзвінок, 95% впевненості):

a.p.g.a.ArrayVsList.CreateArray1         [10.933, 11.097]
a.p.g.a.ArrayVsList.CreateList1          [10.799, 11.046]
a.p.g.a.ArrayVsList.CreateArray10000    [394.899, 404.034]
a.p.g.a.ArrayVsList.CreateList10000     [396.706, 401.266]

Висновок: помітної різниці немає .

отримати операції

Я провів 2 тести, виконавши такі твердження:

  • getList: return list.get(0);
  • getArray: return array[0];

Результати (у наносекундах за дзвінок, 95% впевненості):

a.p.g.a.ArrayVsList.getArray   [2.958, 2.984]
a.p.g.a.ArrayVsList.getList    [3.841, 3.874]

Висновок: отримання з масиву приблизно на 25% швидше ніж отримання з ArrayList, хоча різниця полягає лише в порядку однієї наносекунди.

встановити операції

Я провів 2 тести, виконавши такі твердження:

  • setList: list.set(0, value);
  • setArray: array[0] = value;

Результати (у наносекундах за дзвінок):

a.p.g.a.ArrayVsList.setArray   [4.201, 4.236]
a.p.g.a.ArrayVsList.setList    [6.783, 6.877]

Висновок: задані операції на масивах приблизно на 40% швидші, ніж у списках, але, що стосується отримання, кожна операція набору займає кілька наносекунд - так, щоб різниця досягла 1 секунди, потрібно було б встановити елементи у списку / масиві сотні мільйонів разів!

клон / копія

Конструктор копіювання Делегати ArrayList, щоб Arrays.copyOfтаким чином продуктивність є ідентичною копією масиву (копіювання масиву з допомогою clone, Arrays.copyOfабо System.arrayCopy не має жодної істотної відмінності точки зору продуктивності ).


1
Приємний аналіз. Однак, стосовно вашого коментаря "при роботі з примітивами масиви можуть бути значно швидшими, оскільки вони дозволять уникнути багатьох перетворень боксу / розблокування", ви можете мати свій торт і їсти його теж із списком, підтримуваним примітивним масивом реалізація; наприклад: github.com/scijava/scijava-common/blob/master/src/main/java/org/… . Я насправді дуже здивований, що така річ не перетворилася на основну Java.
ctrueden

2
@ctrueden так, коментар застосовано до стандартного JDK ArrayList. trove4j - добре відома бібліотека, яка підтримує примітивні списки. Java 8 приносить деякі вдосконалення декількома примітивно-спеціалізованими потоками.
assylias

Я не знаю, як працюють показники jmh, але вони враховують компіляцію JIT, що може статися? Продуктивність програми Java може змінюватися з часом, оскільки JVM компілює ваш код.
Гофман

@Hoffmann Так - вона включає етап розминки, який виключається з вимірювання.
assylias

97

Вам слід віддати перевагу загальним типам над масивами. Як зазначають інші, масиви негнучкі і не мають виразної сили родових типів. (Однак вони підтримують перевірку типу виконання, але це погано поєднується з загальними типами.)

Але, як завжди, при оптимізації завжди слід виконувати наступні кроки:

  • Не оптимізуйте, поки у вас не буде приємної, чистої та робочої версії коду. На цьому кроці вже дуже мотивовано перехід на загальний тип.
  • Коли у вас є приємна і чиста версія, вирішіть, чи досить швидка вона.
  • Якщо вона недостатньо швидка, вимірюйте її продуктивність . Цей крок важливий з двох причин. Якщо ви не вимірюєте, ви не знатимете (1) впливу будь-яких оптимізацій, а також (2) знаєте, де оптимізувати.
  • Оптимізуйте найгарячішу частину коду.
  • Поміряйте ще раз. Це так само важливо, як і вимірювання раніше. Якщо оптимізація не покращила речі, відновіть їх . Пам’ятайте, код без оптимізації був чистим, приємним та працюючим.

24

Я здогадуюсь, що оригінальний плакат походить від C ++ / STL фону, що викликає певну плутанину. У C ++ std::list- це подвійний список.

У Java [java.util.]List- інтерфейс без реалізації (чистий абстрактний клас на C ++). Listможе бути подвійно пов'язаний список - java.util.LinkedListнадається. Однак 99 разів із 100, коли ви хочете зробити нове List, ви хочете використовувати java.util.ArrayListзамість цього, що є приблизним еквівалентом C ++ std::vector. Є й інші стандартні реалізації, наприклад, ті, які повернув java.util.Collections.emptyList()іjava.util.Arrays.asList() .

З точки зору продуктивності, дуже маленьке враження від необхідності пройти через інтерфейс та додатковий об'єкт, проте вкладені під час виконання інструменти означають, що це рідко має якесь значення. Також пам’ятайте, що Stringзазвичай це об'єкт плюс масив. Отже, для кожного запису у вас, ймовірно, є два інші об’єкти. У C ++ std::vector<std::string>, хоча копіювання за значенням без вказівника як такого, масиви символів утворюють об'єкт для рядка (і вони зазвичай не поділяються).

Якщо цей конкретний код дійсно чутливий до продуктивності, ви можете створити єдиний char[]масив (або навіть byte[]) для всіх символів усіх рядків, а потім масив компенсацій. IIRC, саме так реалізується javac.


1
Дякую за відповідь. Але ні, я не плутаю список C ++ зі списком інтерфейсів Java. Я поставив запитання таким чином, тому що хотів порівняти ефективність виконання списків, таких як ArrayList та Vector, з необробленими масивами.
euphoria83

І ArrayList, і Vector "зберігають усі дані в безперервному шматку пам'яті".
Том Хотін - тайклін

13

Я погоджуюся, що в більшості випадків вам слід вибрати гнучкість та елегантність ArrayLists над масивами - і в більшості випадків вплив на продуктивність програми буде незначним.

Однак, якщо ви робите постійну, важку ітерацію з невеликими структурними змінами (без додавання та видалення) для, скажімо, програмного забезпечення графічної рендерінгу або спеціальної віртуальної машини, мої тести послідовного контролю доступу показують, що ArrayLists на 1,5 рази повільніше, ніж масиви на моєму система (Java 1.6 на моєму однорічному iMac).

Деякі коди:

import java.util.*;

public class ArrayVsArrayList {
    static public void main( String[] args ) {

        String[] array = new String[300];
        ArrayList<String> list = new ArrayList<String>(300);

        for (int i=0; i<300; ++i) {
            if (Math.random() > 0.5) {
                array[i] = "abc";
            } else {
                array[i] = "xyz";
            }

            list.add( array[i] );
        }

        int iterations = 100000000;
        long start_ms;
        int sum;

        start_ms = System.currentTimeMillis();
        sum = 0;

        for (int i=0; i<iterations; ++i) {
          for (int j=0; j<300; ++j) sum += array[j].length();
        }

        System.out.println( (System.currentTimeMillis() - start_ms) + " ms (array)" );
        // Prints ~13,500 ms on my system

        start_ms = System.currentTimeMillis();
        sum = 0;

        for (int i=0; i<iterations; ++i) {
          for (int j=0; j<300; ++j) sum += list.get(j).length();
        }

        System.out.println( (System.currentTimeMillis() - start_ms) + " ms (ArrayList)" );
        // Prints ~20,800 ms on my system - about 1.5x slower than direct array access
    }
}

Я вважав це цікавою відповіддю, але мені було б цікаво, чи це ще гірше, якщо ArrayList не буде ініціалізований з початковим розміром у пам'яті. Загалом, користь використання ArrayList над власним масивом полягає в тому, що ви цього не знаєте і вам не потрібно хвилюватися. За замовчуванням створюються ArrayLists з початковою довжиною 10, а потім змінюються розміри. Я думаю, що зміна розміру коштує дорого. Я не пробував, очевидно, це порівняльний аналіз.
Зак Паттерсон

4
Цей мікро-орієнтир має недоліки (немає розминки, операції не окремим методом, тому частина арраліста ніколи не оптимізується JIT тощо)
assylias

Я погоджуюся з асиліями. Результатам цього еталону не слід довіряти.
Stephen C

@StephenC Я додав належний мікро-показник (який показує, що операції з отримання порівнянні).
assylias

11

Ну, по-перше, варто уточнити, ви маєте на увазі "список" у класичному сенсі структур даних comp (тобто пов'язаний список) чи ви маєте на увазі java.util.List? Якщо ви маєте на увазі java.util.List, це інтерфейс. Якщо ви хочете використовувати масив, просто використовуйте реалізацію ArrayList, і ви отримаєте поведінку та семантику, схожу на масив. Проблема вирішена.

Якщо ви маєте на увазі масив проти пов’язаного списку, це дещо інший аргумент, для якого ми повертаємось до Big O (ось просте англійське пояснення якщо це незнайомий термін.

Масив;

  • Випадковий доступ: O (1);
  • Вставка: O (n);
  • Видалити: O (n).

Пов'язаний список:

  • Випадковий доступ: O (n);
  • Вставка: O (1);
  • Видалити: O (1).

Таким чином, ви вибираєте те, що найкраще підходить для того, як ви зміните розмір масиву. Якщо ви змінюєте розмір, вставляйте та видаляйте багато, можливо, кращий вибір пов'язаний список. Те саме стосується випадкових випадків. Ви згадуєте серійний доступ. Якщо ви в основному робите послідовний доступ з дуже малою модифікацією, то, мабуть, не має значення, який ви обираєте.

Пов'язані списки мають дещо більший обсяг, оскільки, як ви кажете, ви маєте справу з потенційно непомітними блоками пам'яті та (ефективно) вказівниками на наступний елемент. Це, мабуть, не є важливим фактором, якщо ви не маєте справу з мільйонами записів.


я маю на увазі інтерфейс java.util.List
euphoria83

1
Випадковий доступ O (n) у пов'язаному списку здається мені великою справою.
Бйорн

11

Я написав невеликий орієнтир, щоб порівняти ArrayLists з Arrays. На моєму старовинному ноутбуці час переходу 5000-елементного масиву в 1000 разів був приблизно на 10 мілісекунд повільніше, ніж еквівалентний код масиву.

Отже, якщо ви нічого не робите, але ітератуєте список, і ви багато робите, то, можливо, це варто оптимізації. В іншому випадку я б використовувати список, тому що він буде робити це легше , коли ви робите необхідність оптимізації коду.

пь я зробив повідомлення , що використання for String s: stringsListбуло близько 50% повільніше , ніж при використанні старого стилю для циклу , щоб отримати доступ до списку. Перейдіть до фігури ... Ось дві функції, які я приурочив; масив і список були заповнені 5000 випадковими (різними) рядками.

private static void readArray(String[] strings) {
    long totalchars = 0;
    for (int j = 0; j < ITERATIONS; j++) {
        totalchars = 0;
        for (int i = 0; i < strings.length; i++) {
            totalchars += strings[i].length();

        }
    }
}

private static void readArrayList(List<String> stringsList) {
    long totalchars = 0;
    for (int j = 0; j < ITERATIONS; j++) {
        totalchars = 0;
        for (int i = 0; i < stringsList.size(); i++) {
            totalchars += stringsList.get(i).length();
        }
    }
}

@ Кріс Мей: Чудова робота! Які фактичні час роботи для обох? Чи можете ви сказати мені розмір струн, якими ви користувалися? Крім того, оскільки використання "String s: stringsList" зайняло більше часу, це мій основний страх у використанні більш високих абстракцій на Java взагалі.
euphoria83

Насправді не важливо, як довгі струни для цього mcirobenchmark. Не існує gc, і the char[]не торкається (це не C).
Том Хотін - тайклін

Типовий час для мене склав ~ 25 мс для версії масиву, ~ 35 мс для версії ArrayList. Струни були завдовжки 15-20 символів. Як каже Том, розмір рядка не має великої різниці, в ~ 100-знаковій строці час було приблизно однаковим.
Кріс Травень

3
Як ти вимірював? Наївні вимірювання в мікро-орієнтирах Java зазвичай породжують більше дезінформації, ніж інформації. Остерігайтеся наведеного вище твердження.
jmg

6

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


5
У списку також зберігаються лише посилання на рядки.
Peter Štibraný

6

Якщо у вас є тисячі, подумайте про використання трійника. Трие - це деревоподібна структура, яка об'єднує загальні префікси збереженого рядка.

Наприклад, якщо рядки були

intern
international
internationalize
internet
internets

Трійця буде зберігати:

intern
 -> \0
 international
 -> \0
 -> ize\0
 net
 ->\0
 ->s\0

Для зберігання рядків потрібно 57 символів (включаючи нульовий термінатор, '\ 0') плюс розмір об'єкта String, який їх містить. (По правді кажучи, ми, мабуть, повинні округлити всі розміри до кратних 16, але ...) Назвіть це 57 + 5 = 62 байти, приблизно.

Трие потребує 29 (включаючи нульовий термінатор, '\ 0') для зберігання, плюс розмір вузлів трійки, які є посиланням на масив та список дочірніх вузлів трие.

Для цього прикладу, можливо, виходить приблизно те саме; для тисяч, мабуть, виходить менше, доки у вас є загальні префікси.

Тепер, використовуючи трие в іншому коді, вам доведеться конвертувати в String, ймовірно, використовуючи StringBuffer в якості посередника. Якщо багато рядків використовуються одночасно як Strings, поза межами трие, це втрата.

Але якщо ви використовуєте лише декілька на той час - скажімо, шукати речі у словнику - трійка може заощадити багато місця. Однозначно менше місця, ніж їх зберігання в HashSet.

Ви кажете, що ви отримуєте доступ до них "серійно" - якщо це означає послідовно в алфавітному порядку, трие також очевидно надає вам алфавітний порядок безкоштовно, якщо ви повторите його глибину спочатку.


1
тріє як бібліотека чи як я можу її створити?
euphoria83

Трие може бути корисним лише у разі токенізованих рядків, а не якщо хтось зберігає текстовий текст як рядки.
MN

5

ОНОВЛЕННЯ:

Як зазначив Марк, після розминки JVM суттєвої різниці немає (кілька тестових проходів). Перевіряється заново створений масив або навіть новий пропуск, починаючи з нового рядка матриці. З великою ймовірністю це означає, що простий масив з доступом до індексу не повинен використовуватися на користь колекцій.

І все-таки перші 1-2 проходження простого масиву в 2-3 рази швидше.

ОРИГІНАЛЬНА ПОСТ:

Забагато слів для теми занадто просто, щоб перевірити. Без будь-якого питання масив є в кілька разів швидшим, ніж будь-який контейнер класу . Я біжу над цим питанням, шукаючи альтернативи для мого критичного розділу. Ось прототип коду, який я створив для перевірки реальної ситуації:

import java.util.List;
import java.util.Arrays;

public class IterationTest {

    private static final long MAX_ITERATIONS = 1000000000;

    public static void main(String [] args) {

        Integer [] array = {1, 5, 3, 5};
        List<Integer> list = Arrays.asList(array);

        long start = System.currentTimeMillis();
        int test_sum = 0;
        for (int i = 0; i < MAX_ITERATIONS; ++i) {
//            for (int e : array) {
            for (int e : list) {
                test_sum += e;
            }
        }
        long stop = System.currentTimeMillis();

        long ms = (stop - start);
        System.out.println("Time: " + ms);
    }
}

І ось відповідь:

На основі масиву (рядок 16 активний):

Time: 7064

На основі списку (рядок 17 активний):

Time: 20950

Більше коментарів до "швидшого"? Це цілком зрозуміло. Питання в тому, коли приблизно в 3 рази швидше для вас краще, ніж гнучкість списку. Але це вже інше питання. До речі, я перевірив це також на основі побудованих вручну ArrayList. Майже такий же результат.


2
3рази швидше правда, але незначно так. 14msне давно
0x6C38

1
Тест не розглядає розминку JVM. Змінюйте main () на test () та повторюйте тест виклику з main. До 3-го чи 4-го запуску тесту він працює в багато разів швидше. У цей момент я бачу, що масив приблизно в 9 разів швидший, ніж масив.
Майк

5

Оскільки тут вже є багато хороших відповідей, я хотів би надати вам іншу інформацію практичного огляду, яка полягає в порівнянні вставки та ітерації: примітивний масив проти Linked-list на Java.

Це фактично проста перевірка працездатності.
Отже, результат буде залежати від продуктивності машини.

Для цього використовується вихідний код:

import java.util.Iterator;
import java.util.LinkedList;

public class Array_vs_LinkedList {

    private final static int MAX_SIZE = 40000000;

    public static void main(String[] args) {

        LinkedList lList = new LinkedList(); 

        /* insertion performance check */

        long startTime = System.currentTimeMillis();

        for (int i=0; i<MAX_SIZE; i++) {
            lList.add(i);
        }

        long stopTime = System.currentTimeMillis();
        long elapsedTime = stopTime - startTime;
        System.out.println("[Insert]LinkedList insert operation with " + MAX_SIZE + " number of integer elapsed time is " + elapsedTime + " millisecond.");

        int[] arr = new int[MAX_SIZE];

        startTime = System.currentTimeMillis();
        for(int i=0; i<MAX_SIZE; i++){
            arr[i] = i; 
        }

        stopTime = System.currentTimeMillis();
        elapsedTime = stopTime - startTime;
        System.out.println("[Insert]Array Insert operation with " + MAX_SIZE + " number of integer elapsed time is " + elapsedTime + " millisecond.");


        /* iteration performance check */

        startTime = System.currentTimeMillis();

        Iterator itr = lList.iterator();

        while(itr.hasNext()) {
            itr.next();
            // System.out.println("Linked list running : " + itr.next());
        }

        stopTime = System.currentTimeMillis();
        elapsedTime = stopTime - startTime;
        System.out.println("[Loop]LinkedList iteration with " + MAX_SIZE + " number of integer elapsed time is " + elapsedTime + " millisecond.");


        startTime = System.currentTimeMillis();

        int t = 0;
        for (int i=0; i < MAX_SIZE; i++) {
            t = arr[i];
            // System.out.println("array running : " + i);
        }

        stopTime = System.currentTimeMillis();
        elapsedTime = stopTime - startTime;
        System.out.println("[Loop]Array iteration with " + MAX_SIZE + " number of integer elapsed time is " + elapsedTime + " millisecond.");
    }
}

Результат продуктивності нижче:

введіть тут опис зображення


4

список повільніше, ніж масиви. Якщо вам потрібна ефективність, використовуйте масиви. Якщо вам потрібен список використання гнучкості.


4

Пам’ятайте, що ArrayList інкапсулює масив, тому порівняно з використанням примітивного масиву є невелика різниця (за винятком того, що зі списком набагато простіше працювати з Java).

Практично єдиний час, коли має сенс віддати перевагу масиву ArrayList, це коли ви зберігаєте примітиви, тобто байт, int тощо, і вам потрібна особлива просторова ефективність, яку ви отримуєте, використовуючи примітивні масиви.


4

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

  1. Якщо кількість рядків майже постійне, тоді використовуйте масив (або ArrayList). Але якщо кількість занадто сильно змінюється, то краще використовувати LinkedList.
  2. Якщо є необхідність у додаванні або видаленні елементів посередині, то, звичайно, доведеться використовувати LinkedList.

4

Я прийшов сюди, щоб краще відчути вплив на ефективність використання списків над масивами. Я повинен був адаптувати код тут для свого сценарію: масив / список ~ 1000 ints, використовуючи в основному getters, що означає масив [j] проти list.get (j)

Взявши найкраще з 7, щоб не бути науковим про це (перші кілька із списком, де на 2,5 рази повільніше), я отримую це:

array Integer[] best 643ms iterator
ArrayList<Integer> best 1014ms iterator

array Integer[] best 635ms getter
ArrayList<Integer> best 891ms getter (strange though)

- так, приблизно на 30% швидше з масивом

Друга причина публікації зараз полягає в тому, що ніхто не згадує про вплив, якщо ви робите математику / матрицю / симуляцію / оптимізацію коду з вкладеними петлями.

Скажімо, у вас є три вкладені рівні, а внутрішній цикл удвічі повільніший, ви дивитесь на 8-кратний показник ефективності. Щось, що працюватиме за день, зараз займає тиждень.

* EDIT Досить шокований тут, для ударів я намагався оголосити int [1000], а не Integer [1000]

array int[] best 299ms iterator
array int[] best 296ms getter

Використання Integer [] vs. int [] являє собою подвійне звернення до продуктивності, ListArray з ітератором в 3 рази повільніше, ніж int []. Дійсно, що реалізація списку Java схожа на рідні масиви ...

Код довідки (дзвінок кілька разів):

    public static void testArray()
    {
        final long MAX_ITERATIONS = 1000000;
        final int MAX_LENGTH = 1000;

        Random r = new Random();

        //Integer[] array = new Integer[MAX_LENGTH];
        int[] array = new int[MAX_LENGTH];

        List<Integer> list = new ArrayList<Integer>()
        {{
            for (int i = 0; i < MAX_LENGTH; ++i)
            {
                int val = r.nextInt();
                add(val);
                array[i] = val;
            }
        }};

        long start = System.currentTimeMillis();
        int test_sum = 0;
        for (int i = 0; i < MAX_ITERATIONS; ++i)
        {
//          for (int e : array)
//          for (int e : list)          
            for (int j = 0; j < MAX_LENGTH; ++j)
            {
                int e = array[j];
//              int e = list.get(j);
                test_sum += e;
            }
        }

        long stop = System.currentTimeMillis();

        long ms = (stop - start);
        System.out.println("Time: " + ms);
    }

3

Якщо ви заздалегідь знаєте, наскільки великі дані, то масив буде швидшим.

Список є більш гнучким. Ви можете використовувати ArrayList, який підтримується масивом.


ArrayList має метод secureCapacity (), який попередньо розміщує резервний масив до заданого розміру.
JesperE

Або ви можете вказати розмір під час будівництва. Також "швидше" тут означає "кілька мікросекунд, щоб виділити дві області пам'яті замість однієї"
Аарон Дігулла

3

Це ви можете жити з фіксованим розміром, масиви будуть швидшими і потребують менше пам’яті.

Якщо вам потрібна гнучкість інтерфейсу списку із додаванням та видаленням елементів, залишається питання, яку реалізацію слід вибрати. Часто ArrayList рекомендується та використовується у будь-якому випадку, але також у ArrayList є проблеми з продуктивністю, якщо елементи на початку чи в середині списку потрібно видалити чи вставити.

Тому ви можете поглянути на http://java.dzone.com/articles/gaplist-%E2%80%93-lightning-fast-list, який представляє GapList. Ця нова реалізація списку поєднує в собі сильні сторони як ArrayList, так і LinkedList, що призводить до дуже високої продуктивності майже для всіх операцій.


2

Залежно від реалізації. можливо, масив примітивних типів буде меншим і ефективнішим, ніж ArrayList. Це відбувається тому, що масив буде зберігати значення безпосередньо у суміжному блоці пам'яті, тоді як найпростіша реалізація ArrayList зберігатиме покажчики на кожне значення. Особливо на 64-бітній платформі це може призвести до величезних змін.

Звичайно, можливо, для реалізації jvm є окремий випадок для цієї ситуації, і в цьому випадку продуктивність буде однаковою.


2

Список є кращим способом в java 1.5 і більше, оскільки він може використовувати дженерики. Масиви не можуть мати дженерики. Також масиви мають заздалегідь задану довжину, яка не може динамічно зростати. Ініціалізація масиву з великим розміром - не дуже гарна ідея. ArrayList - це спосіб оголошення масиву за допомогою generics, який може динамічно рости. Але якщо видалення та вставка використовується частіше, то пов'язаний список - це найшвидша структура даних, яку потрібно використовувати.


2

Масиви, рекомендовані скрізь, ви можете використовувати їх замість списку, особливо якщо ви знаєте, що кількість та розмір елементів не змінюватимуться.

Див. Кращі практики Oracle Java: http://docs.oracle.com/cd/A97688_16/generic.903/bp/java.htm#1007056

Звичайно, якщо вам потрібно багато разів додавати та вилучати об’єкти із колекції, просто користуйтеся списками.


Документація, з якою ви пов’язані, більше 10 років, тобто стосується java 1.3. Основні покращення продуктивності були зроблені з тих пір ...
assylias

@assylias див. відповіді вище, вони містять тести на ефективність, що говорить про те, що масиви швидші
Nik

3
Я знаю, що написав одну з них. Але я не думаю, що " масиви рекомендуються скрізь, де ти можеш використовувати їх замість списків " - хороша порада. ArrayList має бути вибором за замовчуванням у більшості ситуацій, якщо ви не маєте справу з примітивами і ваш код не відрізняється від продуктивності.
assylias

2

Ні в одній з відповідей не було інформації, яка мене зацікавила - багаторазове сканування одного і того ж масиву багато разів. Для цього довелося створити тест JMH.

Результати (Java 1.8.0_66 x32, ітераційний простий масив принаймні у 5 разів швидший, ніж ArrayList):

Benchmark                    Mode  Cnt   Score   Error  Units
MyBenchmark.testArrayForGet  avgt   10   8.121 ? 0.233  ms/op
MyBenchmark.testListForGet   avgt   10  37.416 ? 0.094  ms/op
MyBenchmark.testListForEach  avgt   10  75.674 ? 1.897  ms/op

Тест

package my.jmh.test;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
import org.openjdk.jmh.annotations.Benchmark;
import org.openjdk.jmh.annotations.BenchmarkMode;
import org.openjdk.jmh.annotations.Fork;
import org.openjdk.jmh.annotations.Measurement;
import org.openjdk.jmh.annotations.Mode;
import org.openjdk.jmh.annotations.OutputTimeUnit;
import org.openjdk.jmh.annotations.Scope;
import org.openjdk.jmh.annotations.State;
import org.openjdk.jmh.annotations.Warmup;

@State(Scope.Benchmark)
@Fork(1)
@Warmup(iterations = 5, timeUnit = TimeUnit.SECONDS)
@Measurement(iterations = 10)
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.MILLISECONDS)
public class MyBenchmark {

    public final static int ARR_SIZE = 100;
    public final static int ITER_COUNT = 100000;

    String arr[] = new String[ARR_SIZE];
    List<String> list = new ArrayList<>(ARR_SIZE);

    public MyBenchmark() {
        for( int i = 0; i < ARR_SIZE; i++ ) {
            list.add(null);
        }
    }

    @Benchmark
    public void testListForEach() {
        int count = 0;
        for( int i = 0; i < ITER_COUNT; i++ ) {
            for( String str : list ) {
                if( str != null )
                    count++;
            }
        }
        if( count > 0 )
            System.out.print(count);
    }

    @Benchmark
    public void testListForGet() {
        int count = 0;
        for( int i = 0; i < ITER_COUNT; i++ ) {
            for( int j = 0; j < ARR_SIZE; j++ ) {
                if( list.get(j) != null )
                    count++;
            }
        }
        if( count > 0 )
            System.out.print(count);
    }

    @Benchmark
    public void testArrayForGet() {
        int count = 0;
        for( int i = 0; i < ITER_COUNT; i++ ) {
            for( int j = 0; j < ARR_SIZE; j++ ) {
                if( arr[j] != null )
                    count++;
            }
        }
        if( count > 0 )
            System.out.print(count);
    }

}

2

"Тисячі" - це не велика кількість. Кілька тисяч рядків довжиною абзацу мають розмір на пару мегабайт. Якщо все, що ви хочете зробити, це отримати доступ до цих серійно, скористайтеся незмінним спільно пов’язаним списком .


8 байт у більшості 64-бітних реалізацій.
Том Хотін - тайклін

Чи є докази того, що ця річ швидша, ніж java.util.LinkedList? Що також "в пам'яті"? Це також може бути непорушним, начебто це має якесь значення.
Маркіз Лорн

1

Не потрапляйте в пастку оптимізації без належного бенчмаркінгу. Як і інші запропонували використовувати профілер, перш ніж робити будь-яке припущення.

Різні структури даних, які ви перерахували, мають різні цілі. Список дуже ефективний при вставці елементів на початку та в кінці, але сильно страждає під час доступу до випадкових елементів. Масив має фіксовану пам’ять, але забезпечує швидкий випадковий доступ. Нарешті, ArrayList покращує інтерфейс до масиву, дозволяючи йому рости. Зазвичай структура даних, що використовується, повинна диктуватися тим, як зберігаються дані будуть доступними або доданими.

Про споживання пам'яті. Ви ніби змішуєте деякі речі. Масив дасть вам безперервний фрагмент пам'яті лише для типу даних, які у вас є. Не забувайте, що java має фіксовані типи даних: boolean, char, int, long, float та Object (сюди входять усі об'єкти, навіть масив є Object). Це означає, що якщо ви оголосите масив рядків String [1000] або MyObject myObjects [1000], ви отримаєте лише поле 1000 пам'яті, достатньо велике для зберігання місця розташування (посилань або покажчиків) об'єктів. Ви не отримаєте 1000 ящиків пам'яті достатньо великих розмірів для розміру об'єктів. Не забувайте, що ваші об’єкти спочатку створюються за допомогою "нового". Це коли відбувається розподіл пам’яті, а пізніше в масиві зберігається посилання (їх адреса пам’яті). Об'єкт не копіюється в масив, лише його посилання.


1

Я не думаю, що це суттєво має значення для Strings. Що є суміжним у масиві рядків, це посилання на рядки, самі рядки зберігаються у випадкових місцях пам'яті.

Масиви проти списків можуть змінити значення примітивних типів, а не об'єктів. Якщо ви заздалегідь знаєте кількість елементів, і вам не потрібна гнучкість, масив мільйонів цілих чисел або подвоєнь буде більш ефективним в пам’яті і незначною швидкістю, ніж список, оскільки вони дійсно будуть зберігатися безперервно і доступ до них миттєво. Ось чому Java все ще використовує масиви символів для рядків, масиви вкладок для даних зображень тощо.



1

Дуже багато мікробензиків, наведених тут, знайшли цифри в кілька наносекунд для таких речей, як читання масиву / ArrayList. Це цілком розумно, якщо все є у вашому кеш-пам'яті L1.

Кеш вищого рівня або доступ до основної пам’яті можуть мати порядок збільшення приблизно 10nS-100nS, порівняно з 1nS для кешу L1. Доступ до ArrayList має додаткове опосередковане використання пам’яті, і в реальній програмі ви могли платити цю ціну майже будь-що майже ніколи не залежно від того, що ваш код робить між доступом. І, звичайно, якщо у вас є багато невеликих списків ArrayLis, це може доповнити використання вашої пам’яті та зробить ймовірніше, що у вас будуть пропуски кешу.

Оригінальний плакат, як видається, використовує лише один і отримує доступ до великої кількості вмісту за короткий час, тому це не повинно бути великих труднощів. Але для інших людей це може бути інакше, і вам слід бути уважними при інтерпретації мікропоказів.

Однак рядки Java жахливо марнотратні, особливо якщо ви зберігаєте безліч маленьких (просто подивіться на них за допомогою аналізатора пам'яті, здається, це> 60 байт для рядка з кількох символів). Масив рядків має напрямок до об'єкта String, а інший - від об'єкта String до char [], який містить саму рядок. Якщо щось вдарить ваш кеш L1, це в поєднанні з тисячами або десятками тисяч рядків. Тож, якщо ви серйозно - дуже серйозно - з тим, щоб викреслити якомога більше продуктивності, тоді ви можете поглянути на те, як це зробити інакше. Ви можете, скажімо, утримувати два масиви, знак char [] з усіма рядками в ньому, один за одним, і int [] із зрушеннями до початку. Це буде PITA, щоб зробити що-небудь, і ви майже напевно цього не потребуєте. А якщо так, то


0

Це залежить від способу доступу до нього.

Після зберігання, якщо ви в основному хочете виконати пошукову операцію, з невеликою або без вставки / видалення, перейдіть до масиву (так як пошук проводиться в масиві O (1), тоді як для додавання / видалення може знадобитися переупорядкування елементів) .

Якщо ви зберігаєте, якщо вашою основною метою є додавання / видалення рядків, з незначною операцією пошуку або без неї, тоді перейдіть до списку.


0

ArrayList внутрішньо використовує об’єкт масиву для додавання (або збереження) елементів. Іншими словами, ArrayList підтримується даними -структури Array. Масив ArrayList може змінюватись (або динамічно).

Масив швидше, ніж Array оскільки ArrayList використовує внутрішньо масив. якщо ми можемо безпосередньо додавати елементи в масив і опосередковано додавати елементи в масив через ArrayList, завжди безпосередньо механізм швидше, ніж непрямий механізм.

У класі ArrayList є два перевантажених методу add ():
1 add(Object) .: додає об'єкт до кінця списку.
2 add(int index , Object ) .: вставляє вказаний об'єкт у вказане положення у списку.

Як динамічно зростає розмір ArrayList?

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

Важливим моментом, що слід звернути увагу на вищезазначений код, є те, що ми перевіряємо ємність ArrayList, перш ніж додавати елемент. secureCapacity () визначає, який поточний розмір зайнятих елементів та який максимальний розмір масиву. Якщо розмір заповнених елементів (включаючи новий елемент, який потрібно додати до класу ArrayList) перевищує максимальний розмір масиву, то збільште розмір масиву. Але розмір масиву не можна динамічно збільшувати. Тож, що відбувається всередині, це новий масив, створений з ємністю

До Java 6

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

(Оновлення) Від Java 7

 int newCapacity = oldCapacity + (oldCapacity >> 1);

також дані зі старого масиву копіюються в новий масив.

Маючи накладні методи в ArrayList, тому Array швидше, ніж ArrayList.


0

Масиви - завжди було б краще, коли нам доведеться досягти швидшого отримання результатів

Списки. Виконує результати вставки та видалення, оскільки вони можуть бути виконані в O (1), і це також забезпечує способи легко додавати, отримувати та видаляти дані. Набагато простіше у використанні.

Але завжди пам’ятайте, що отримання даних буде швидким, коли положення індексу в масиві, де зберігаються дані, відомо.

Цього можна було досягти добре, сортуючи масив. Отже, це збільшує час отримання даних (тобто; зберігання даних + сортування даних + пошук позиції, де дані знайдені). Отже, це збільшує додаткову затримку для отримання даних з масиву, навіть якщо вони можуть бути хорошими для швидшого отримання даних.

Отже, це можна вирішити за допомогою структури даних трійки або потрійної структури даних. Як було обговорено вище, структура даних трійки була б дуже ефективною в пошуку даних, пошук конкретного слова можна здійснити з величиною O (1). Коли час має значення, тобто; якщо вам доведеться швидко шукати та отримувати дані, ви можете скористатись структурою даних trie.

Якщо ви хочете, щоб ваш об'єм пам’яті був витрачений менше, і ви хочете мати кращі показники, тоді перейдіть до потрійної структури даних. Обидва ці програми підходять для зберігання величезної кількості рядків (наприклад, як слова, що містяться у словнику).

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