Що ефективніше: System.arraycopy або Arrays.copyOf?


77

toArrayМетод ArrayList, Блох використовує як System.arraycopyі Arrays.copyOfскопіювати масив.

public <T> T[] toArray(T[] a) {
    if (a.length < size)
        // Make a new array of a's runtime type, but my contents:
        return (T[]) Arrays.copyOf(elementData, size, a.getClass());
    System.arraycopy(elementData, 0, a, 0, size);
    if (a.length > size)
        a[size] = null;
    return a;
}

Як я можу порівняти ці два способи копіювання і коли слід використовувати який?


1
Що таке "Bloch"? Чому фрагмент коду актуальний?
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功

6
@Ciro Bloch - хлопець, який написав реалізацію ArrayList.
Insomniac

1
Дивіться також: stackoverflow.com/q/44487304/14955
Тіло

Відповіді:


107

Різниця полягає в тому, Arrays.copyOfщо не тільки копіюються елементи, але й створюється новий масив. System.arraycopyкопіює в наявний масив.

Ось джерело для Arrays.copyOf, як ви бачите, воно використовується System.arraycopyвсередині для заповнення нового масиву:

public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) {
    T[] copy = ((Object)newType == (Object)Object[].class)
        ? (T[]) new Object[newLength]
        : (T[]) Array.newInstance(newType.getComponentType(), newLength);
    System.arraycopy(original, 0, copy, 0,
                     Math.min(original.length, newLength));
    return copy;
}

2
Це очевидно .... Arrays.copyOf повинен створити новий екземпляр масиву, оскільки ми йому не передаємо його; поки ми передаємо пункт призначення System.arraycopy. Що стосується швидкості, знову ж очевидно, що System.arraycopy повинен виграти, оскільки це власний метод, і зрештою Arrays.copyOf також викликає цей метод для копіювання масиву.
Pravat Panda

4
Джерело Java не має значення з моменту створення внутрішньої інформації.
maaartinus

також System.arraycopyмає більш докладні параметри, ви можете вказати індекс запуску для обох масивів.
M.kazem Akhgary

відкритий клас TestClass {public static void main (String [] args) {byte a [] = {65, 66, 67, 68, 69, 70, 71, 72, 73, 74}; System.arraycopy (a, 0, a, 1, a.length - 1); System.out.println (новий рядок (a)); }}
Раджат

код вище друкує AABCDEFGHI, що означає, що він створює новий масив.
Раджат

41

Незважаючи на те System.arraycopy, що реалізовано власно, і, отже, може бути на 1 швидше, ніж цикл Java, це не завжди так швидко, як можна було б очікувати. Розглянемо цей приклад:

Object[] foo = new Object[]{...};
String[] bar = new String[foo.length];

System.arraycopy(foo, 0, bar, 0, bar.length);

У цьому випадку масиви fooта barмають різні базові типи, тому реалізація arraycopyповинна перевірити тип кожного скопійованого посилання, щоб переконатися, що це насправді посилання на екземпляр String. Це значно повільніше, ніж простий C-стиль memcopyвмісту масиву.

Інший момент - Arrays.copyOfвикористання System.arraycopyпід капотом. Тому System.arraycopyце на обличчі його не повинно бути менше 2 , ніж Arrays.copyOf. Але ви можете бачити (з наведеного вище коду ), що Arrays.copyOfв деяких випадках використовуватиме відображення для створення нового масиву. Тож порівняння продуктивності не є простим.

У цьому аналізі є кілька недоліків.

  1. Ми розглядаємо код реалізації з конкретної версії Java. Ці методи можуть змінюватися, скасовуючи попередні припущення щодо ефективності.

  2. Ми ігноруємо можливість того, що компілятор JIT міг би виконати розумну оптимізацію особливих випадків для цих методів. І, мабуть, це трапляється з Arrays.copyOf; див. Чому Arrays.copyOf в 2 рази швидше, ніж System.arraycopy для малих масивів? . Цей метод є "властивим" у реалізаціях Java поточного покоління, що означає, що компілятор JIT буде ігнорувати те, що міститься у вихідному коді Java!

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


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


1
+1 за вказівку на перевірку типу, яка іноді має відбуватися.
Тіло

@StephenC, Що стосується вашого першого абзацу, чи є випадки, коли System.arrayCopyнасправді могло бути повільніше ?
Pacerier

@Pacerier - я не знаю про це. Але це не неможливо.
Stephen C

1
Трохи пізно з цією відповіддю, але маючи на увазі, що на System.arrayCopy посилається Arrays.copyOf (як уже згадувалося у прийнятій відповіді), мені доведеться сказати, що System.arrayCopy не може бути повільнішим. Звичайно, це передбачало, що це не спеціальна реалізація JVM, а спеціальна реалізація методів.
Уейн,

@Wayne - Ви робите припущення щодо оптимізації, яку робить компілятор JIT. Можливо, він повністю ігнорує вихідний код / ​​байт-коди, які ви переглядаєте. Або що це може зробити це в якійсь майбутній версії Java. Або що вихідний код може відрізнятися в якійсь іншій версії. Або що є якась розумна оптимізація, яка може бути виконана під час прямого arraycopyдзвінка, а не під час непрямого дзвінка. Все це можливо ... якщо не правдоподібно.
Стівен С

14

Якщо вам потрібна точна копія масиву (скажімо, якщо ви хочете зробити захисну копію), найефективнішим способом копіювання масиву є, мабуть, використання clone()методу об’єкта масиву :

class C {
    private int[] arr;
    public C(int[] values){
        this.arr = values.clone();
    }
}

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

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


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

@Pacerier "Я не часто використовую clone... але коли я використовую, я використовую його на масивах."
gustafc

Я говорив про це, .clone() коли використовувався на масивах .... У будь-якому випадку, він все одно повертає Об'єкт, який потім повинен бути відлитий назад у масив (= додаткова робота).
Pacerier

@Pacerier насправді він перевантажений правильним типом повернення для всіх типів масивів, тому вам не потрібно робити будь-який кастинг самостійно, і не впевнено, що ВМ доведеться перевіряти будь-які заливки.
gustafc

Хороший момент, отже, ви говорите, що рідна мова clone()буде швидшою, ніж рідна, System.arrayCopyтому що clone()вона також присвячена цьому завданням?
Pacerier

13

System.arrayCopy набагато швидше. Це в системі, оскільки використовує пряму копію пам'яті поза межами Java Java. Використовуйте його, коли це можливо.


1
Але System.arraycopy копіює лише наявний масив. Arrays.copyOf також створює для вас вихідний масив.
Тіло

2
Це правда, і поки ви використовуєте System.arraycopy під ковдрами, будь-яка зручна обгортка, яку ви використовуєте, є просто підливою.
Ry4an Brase

4
І, бувають випадки, коли System.arrayCopy не вдається скористатися прямою копією пам'яті.
Stephen C

12

Ви розглядали реалізацію Arrays.copyOf () від Sun?

 public static int[] copyOf(int[] original, int newLength) {
    int[] copy = new int[newLength];
    System.arraycopy(original, 0, copy, 0,
                     Math.min(original.length, newLength));
    return copy;
}

Як бачимо, він використовує System.arraycopy()внутрішньо, тому продуктивність буде однаковою.



2

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

байт [] тест продуктивності копіювання

10000 000 ітерацій 40b array.copyOfRange: 135ms systems.arraycopy: 141ms

10000 000 ітерацій 1000b array.copyOfRange: 1861ms systems.arraycopy: 2211ms

10000 000 ітерацій 4000b array.copyOfRange: 6315ms systems.arraycopy: 5251ms

1000000 ітерацій 100000b array.copyOfRange: 15198ms систем. Arraycopy: 14783ms


1
Не впевнений, чому голос проти, це було доречно та корисно для обговорення.
кіберзагроза

1
Я здогадуюсь, тому що ви не згадали про свій JDK, ОС, HW і не опублікували свій вихідний код, щоб перевірити його.
Ганс Вурст,

1

Якщо ви подивитесь як на вихідний код System.arraycopy () of, так і на Array.copyOf (), для продуктивності.

System.arraycopy () - це код С, він працює безпосередньо на вашому масиві, не повертає жодних значень, і тому він повинен працювати набагато швидше, ніж Array.copyOf (). Тим не менше, якщо вам потрібно створити новий масив або якщо вам просто потрібно значення операції копіювання, вам доведеться створити новий масив для цього, встановити нову довжину масиву тощо ... Таким чином, ви не можете зробити повертається System.arraycopy (джерело, 0, адресат, 0, довжина).

Для того, що тоді може зробити Array.copyOf (), він створить для вас новий масив. Ви можете призначити повернене значення з Array.copyOf () масиву або повернути його із методу, оскільки Array.copyOf () повертає вам значення, замість того, щоб працювати безпосередньо з цільовим масивом. Таким чином, ваш код буде виглядати набагато чистішим. Тим не менше, для вартості продуктивності Array.copyOf () є загальним методом типу, і він не знає заздалегідь, з чим буде працювати. Таким чином, він повинен викликати Array.newInstance () або new Object (), а потім передати його типу масиву вводу.

Тож підбиваємо підсумки. Використовуйте System.arraycopy () через продуктивність. Використовуйте Array.copyOf () для більш чистого коду.


-1
      class ArrayCopyDemo {
   public static void main(String[] args) {
    char[] copyFrom = { 'd', 'e', 'c', 'a', 'f', 'f', 'e',
            'i', 'n', 'a', 't', 'e', 'd' };
    char[] copyTo = new char[7];

    System.arraycopy(copyFrom, 2, copyTo, 0, 7);
    System.out.println(new String(copyTo));
}
 }

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