Зробіть копію масиву


345

У мене є масив, aякий постійно оновлюється. Скажімо a = [1,2,3,4,5]. Мені потрібно зробити точну копію копії aта зателефонувати b. Якби aзмінитись [6,7,8,9,10], bвсе одно було б [1,2,3,4,5]. Який найкращий спосіб зробити це? Я спробував forцикл, як:

for(int i=0; i<5; i++) {
    b[i]=a[i]
}

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

Відповіді:


558

Ви можете спробувати використати System.arraycopy ()

int[] src  = new int[]{1,2,3,4,5};
int[] dest = new int[5];

System.arraycopy( src, 0, dest, 0, src.length );

Але, напевно, краще використовувати clone () у більшості випадків:

int[] src = ...
int[] dest = src.clone();

9
+1 за невідхилення колеса. І наскільки я знаю, це рішення тим швидше ви можете отримати копіювання масиву.
Феліпе Гуммель

6
і клон, і арракопія є рідними. Я б очікував, що клон буде незначно швидшим. не те, що різниця має значення.
MeBigFatGuy

5
@Felipe, @MeBigFatGuy - лише для великого масиву. Для невеликого масиву цикл копіювання може бути швидшим через накладні витрати. Якщо ви подивитеся на javadoc для System.arraycopy, ви побачите, що метод повинен перевірити різні речі перед його запуском. Деякі з цих перевірок непотрібні з циклом копіювання, залежно від типів статичного масиву.
Стівен C

7
@FelipeHummel, @MeBigFatGuy, @StephenC - Ось тест на ефективність методів копіювання масиву, згаданих у відповідях тут. У цьому наборі clone()виявляється найшвидшим на 250 000 елементів.
Адам

6
Розчаровує те, що вся дискусія тут стосується питань мікропродуктивності, які 99,999% часу не мають значення. Більш важливий момент - цеsrc.clone() він читає і має набагато менше можливостей для помилок, ніж виділення нового масиву та виконання arraycopy. (І теж буває швидко.)
Брайан Гец

231

ви можете використовувати

int[] a = new int[]{1,2,3,4,5};
int[] b = a.clone();

так само.


6
Я просто уточнюю думку ОП, що: " Якщо A змінився на [6,7,8,9,10], B все одно повинен бути [1,2,3,4,5] ". ОП заявив, що він намагався використовувати цикл, але йому не вийшло.
Гаррі Радість

15
Акторський склад зайвий; хороший статичний аналізатор попередить про це. Але клонування, безумовно, найкращий спосіб зробити нову копію масиву.
erickson

5
@MeBigFatGuy - випадок використання ОП тягне за собою повторне копіювання в той самий масив, тому клон не працює.
Стівен C

4
@Stephen C, я цього не читав. Я просто прочитав, що він хоче копію, а потім згодом буде неодноразово оновлювати неприховану версію.
MeBigFatGuy

4
@MeBigFatGuy - він сказав: "У мене є масив A, який постійно оновлюється". . Можливо, я занадто багато читаю в цьому, але я вважаю, що він також багато разів копіює від А до Б.
Стівен C

184

Якщо ви хочете зробити копію:

int[] a = {1,2,3,4,5};

Це шлях:

int[] b = Arrays.copyOf(a, a.length);

Arrays.copyOfможе бути швидше, ніж a.clone()на малих масивах. Обидва елементи копіювання однаково швидко повертаються, але clone () повертається, Objectтому компілятор повинен вставити неявний каст int[]. Ви можете бачити його в байт-коді, приблизно так:

ALOAD 1
INVOKEVIRTUAL [I.clone ()Ljava/lang/Object;
CHECKCAST [I
ASTORE 2

62

Приємне пояснення від http://www.journaldev.com/753/how-to-copy-arrays-in-java

Методи копіювання Java Array

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

System.arraycopy () : Масив системного класу () - найкращий спосіб зробити часткову копію масиву. Це дає простий спосіб визначити загальну кількість елементів для копіювання та позиції індексу масиву джерела та призначення. Наприклад, System.arraycopy (джерело, 3, призначення, 2, 5) буде копіювати 5 елементів від джерела до місця призначення, починаючи з 3-го індексу джерела до 2-го індексу призначення.

Arrays.copyOf (): Якщо ви хочете скопіювати перші кілька елементів масиву або повну копію масиву, ви можете скористатися цим методом. Очевидно, він не є універсальним, як System.arraycopy (), але він також не заплутаний і простий у використанні.

Arrays.copyOfRange () : Якщо ви хочете скопіювати кілька елементів масиву, де початковий індекс не дорівнює 0, ви можете використовувати цей метод для копіювання часткового масиву.


35

У мене є відчуття, що всі ці "кращі способи копіювання масиву" насправді не вирішать вашу проблему.

Ти кажеш

Я спробував цикл на зразок [...], але це, здається, не працює правильно?

Дивлячись на цю петлю, немає очевидних причин, щоб вона не працювала ... якщо тільки:

  • у вас якимось чином aі bмасиви переплутані (наприклад, aі bвідносяться до того ж масиву), або
  • ваш додаток є багатопоточним і різні потоки читають і оновлюють aмасив одночасно.

В будь-якому випадку альтернативні способи копіювання не вирішать основної проблеми.

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

(У вашому запитанні є підказки, які змушують мене думати, що це насправді пов’язано з нитками; наприклад, ваше твердження, яке aпостійно змінюється.)


2
погодився .. напевно, правда.
MeBigFatGuy


9

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

int[] a = {1,2,3,4,5};
int[] b = Arrays.copyOf(a, a.length);
int[] c = a.clone();

//What if array a comes as local parameter? You need to use null check:

public void someMethod(int[] a) {
    if (a!=null) {
        int[] b = Arrays.copyOf(a, a.length);
        int[] c = a.clone();
    }
}

Я рекомендую вам не вигадувати колесо та використовувати корисний клас там, де всі необхідні перевірки вже виконані. Розглянемо ArrayUtils з apache commons. Код стає коротшим:

public void someMethod(int[] a) {
    int[] b = ArrayUtils.clone(a);
}

Ви можете знайти там Apache


8

Ви також можете використовувати Arrays.copyOfRange.

Приклад :

public static void main(String[] args) {
    int[] a = {1,2,3};
    int[] b = Arrays.copyOfRange(a, 0, a.length);
    a[0] = 5;
    System.out.println(Arrays.toString(a)); // [5,2,3]
    System.out.println(Arrays.toString(b)); // [1,2,3]
}

Цей метод схожий на Arrays.copyOf, але він більш гнучкий. Вони обидва використовують System.arraycopyпід кришкою.

Дивіться :


3

Для безпечної копії масиву ви також можете використовувати необов'язковий Object.clone()метод, передбачений у цій відповіді .

int[] arrayToCopy = {1, 2, 3};
int[] copiedArray = Optional.ofNullable(arrayToCopy).map(int[]::clone).orElse(null);

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

1
Я не згоден, масив буде на купі спеціально для цієї конструкції. Дійсно, він викликає клон лише тоді, коли це потрібно, і Optionalоб'єкт є лише порожнім об'єктом із посиланням на існуючий масив. Щодо впливу на продуктивність, я б сказав, що поки що рано, говорити, що це насправді вплив, оскільки цей тип конструкції є хорошим кандидатом для вбудовування всередині JVM і не має більшого впливу, ніж інші методи. Справа в стилі (функціональне програмування проти процедурного програмування, але не тільки) вважати його складнішим чи ні.
Ніколя Генно

3

Якщо ви повинні працювати з сирими масивами , а не ArrayListто Arraysє те , що вам потрібно. Якщо ви подивитеся на вихідний код, це абсолютно найкращі способи отримати копію масиву. У них є хороший захисний програмування, оскільки System.arraycopy()метод викидає безліч неперевірених винятків, якщо ви подаєте його нелогічними параметрами.

Ви можете використовувати будь- Arrays.copyOf()який, який буде копіювати з першого Nthелемента в новий коротший масив.

public static <T> T[] copyOf(T[] original, int newLength)

Копіює вказаний масив, обрізаючи або замінюючи нулями (якщо потрібно), щоб копія мала вказану довжину. Для всіх індексів, які дійсні як в оригінальному масиві, так і в копії, два масиви будуть містити однакові значення. Для будь-яких індексів, які дійсні в копії, але не в оригіналі, копія буде містити null. Такі індекси існуватимуть лише тоді, коли вказана довжина перевищує довжину вихідного масиву. Отриманий масив точно такого ж класу, як і вихідний масив.

2770
2771    public static <T,U> T[] More ...copyOf(U[] original, int newLength, Class<? extends T[]> newType) {
2772        T[] copy = ((Object)newType == (Object)Object[].class)
2773            ? (T[]) new Object[newLength]
2774            : (T[]) Array.newInstance(newType.getComponentType(), newLength);
2775        System.arraycopy(original, 0, copy, 0,
2776                         Math.min(original.length, newLength));
2777        return copy;
2778    }

або Arrays.copyOfRange()також зробить трюк:

public static <T> T[] copyOfRange(T[] original, int from, int to)

Копіює вказаний діапазон вказаного масиву в новий масив. Початковий індекс діапазону (від) повинен лежати між нулем та початковою. Довжиною, включно. Значення в оригіналі [від] розміщується в початковому елементі копії (якщо тільки з == original.length або від == до). Значення наступних елементів у вихідному масиві розміщуються на наступні елементи в копії. Кінцевий індекс діапазону (до), який повинен бути більшим або рівним від, може бути більшим, ніж original.length, в цьому випадку нуль розміщується у всіх елементах копії, індекс яких більший або рівний оригіналу. довжина - від. Довжина повернутого масиву буде до - від. Отриманий масив точно такого ж класу, як і вихідний масив.

3035    public static <T,U> T[] More ...copyOfRange(U[] original, int from, int to, Class<? extends T[]> newType) {
3036        int newLength = to - from;
3037        if (newLength < 0)
3038            throw new IllegalArgumentException(from + " > " + to);
3039        T[] copy = ((Object)newType == (Object)Object[].class)
3040            ? (T[]) new Object[newLength]
3041            : (T[]) Array.newInstance(newType.getComponentType(), newLength);
3042        System.arraycopy(original, from, copy, 0,
3043                         Math.min(original.length - from, newLength));
3044        return copy;
3045    }

Як бачимо, обидва ці функції - лише обгорткові функції, System.arraycopyзахищені логікою, що те, що ви намагаєтеся зробити, є дійсним.

System.arraycopy це абсолютний найшвидший спосіб копіювання масивів.


0

У мене була схожа проблема з 2D-масивами і закінчилася тут. Я копіював основний масив і змінював значення внутрішніх масивів, і був здивований, коли значення змінилися в обох копіях. В основному обидві копії були незалежними, але містили посилання на одні й ті ж внутрішні масиви, і мені довелося зробити масив копій внутрішніх масивів, щоб отримати те, що я хотів.

Це, мабуть, не проблема ОП, але я сподіваюся, що це все ще може бути корисним.

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