Zrób kopię tablicy


345

Mam tablicę, aktóra jest stale aktualizowana. Powiedzmy Chodźmy a = [1,2,3,4,5]. Muszę zrobić dokładną kopię ai zadzwonić b. Gdyby asię zmienić [6,7,8,9,10], bpowinno być nadal [1,2,3,4,5]. Jak najlepiej to zrobić? Próbowałem forpętli:

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

ale to nie działa poprawnie. Nie używaj zaawansowanych terminów, takich jak głębokie kopiowanie itp., Ponieważ nie wiem, co to oznacza.

Odpowiedzi:


558

Możesz spróbować użyć System.arraycopy ()

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

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

Ale prawdopodobnie lepiej użyć clone () w większości przypadków:

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

9
+1 za brak ponownego wjechania w koło. I o ile mi wiadomo, to rozwiązanie jest tym szybsze, że można uzyskać kopiowanie tablicy.
Felipe Hummel

6
zarówno klon, jak i arachykopy są rodzime. Spodziewałbym się, że klon będzie nieznacznie szybszy. nie to, że różnica ma znaczenie.
MeBigFatGuy

5
@Felipe, @MeBigFatGuy - tylko dla dużej tablicy. W przypadku małej tablicy pętla kopiowania może być szybsza z powodu narzutów związanych z konfiguracją. Jeśli spojrzysz na javadoc System.arraycopy, zobaczysz, że metoda musi sprawdzić różne rzeczy przed jej uruchomieniem. Niektóre z tych kontroli są niepotrzebne w przypadku pętli kopiowania, w zależności od statycznych typów tablic.
Stephen C

7
@FelipeHummel, @MeBigFatGuy, @StephenC - Oto test wydajności metod kopiowania tablic wymienionych w odpowiedziach tutaj. W tej konfiguracji clone()okazuje się najszybszy dla 250 000 elementów.
Adam

6
Rozczarowujące jest to, że cała dyskusja dotyczy zagadnień związanych z mikro-wydajnością, które w 99,999% przypadków nie mają znaczenia. Ważniejsze jest to, że src.clone()jest bardziej czytelny i ma znacznie mniejszą szansę na błąd niż przydzielenie nowej tablicy i zrobienie tego arraycopy. (A także zdarza się, że jest szybki.)
Brian Goetz

231

możesz użyć

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

także.


6
Ja tylko wyjaśniam punkt OP, że: „ Jeśli A miałoby zmienić się na [6,7,8,9,10], B powinien nadal wynosić [1,2,3,4,5] ”. OP powiedział, że próbował użyć pętli, ale dla niego nie działał.
Harry Joy,

15
Obsada jest niepotrzebna; dobry analizator statyczny ostrzeże o tym. Ale klonowanie jest zdecydowanie najlepszym sposobem na utworzenie nowej kopii tablicy.
erickson

5
@MeBigFatGuy - przypadek użycia OP wymaga wielokrotnego kopiowania do tej samej tablicy, więc klon nie działa.
Stephen C

4
@Stephen C, nie przeczytałem tego. Właśnie przeczytałem, że chce kopii, a następnie będzie wielokrotnie aktualizować wersję bez skrytki.
MeBigFatGuy

4
@MeBigFatGuy - powiedział „Mam tablicę A, która jest ciągle aktualizowana”. . Może czytam w tym zbyt wiele, ale uważam, że sugeruje to, że wielokrotnie kopiuje również A do B.
Stephen C

184

Jeśli chcesz zrobić kopię:

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

To jest odpowiednie rozwiązanie:

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

Arrays.copyOfmoże być szybszy niż a.clone()na małych tablicach. Oba elementy kopiują równie szybko, ale Objectfunkcja clone () zwraca, więc kompilator musi wstawić niejawne rzutowanie int[]. Możesz to zobaczyć w kodzie bajtowym, coś takiego:

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

62

Ładne wyjaśnienie z http://www.journaldev.com/753/how-to-copy-arrays-in-java

Metody kopiowania tablicy Java

Object.clone () : Klasa Object zapewnia metodę clone (), a ponieważ tablica w java jest również Objectem , możesz użyć tej metody do uzyskania pełnej kopii tablicy. Ta metoda nie będzie Ci odpowiadać, jeśli chcesz częściową kopię tablicy.

System.arraycopy () : arraycopy klasy systemowej () jest najlepszym sposobem na częściową kopię tablicy. Zapewnia łatwy sposób na określenie całkowitej liczby elementów do skopiowania oraz pozycji indeksu tablicy źródłowej i docelowej. Na przykład System.arraycopy (source, 3, destination, 2, 5) skopiuje 5 elementów ze źródła do miejsca docelowego, zaczynając od trzeciego indeksu źródła do drugiego indeksu miejsca docelowego.

Arrays.copyOf (): Jeśli chcesz skopiować kilka pierwszych elementów tablicy lub pełną kopię tablicy, możesz użyć tej metody. Oczywiście nie jest wszechstronny jak System.arraycopy (), ale nie jest też mylący i łatwy w użyciu.

Arrays.copyOfRange () : Jeśli chcesz skopiować kilka elementów tablicy, gdzie indeks początkowy nie jest równy 0, możesz użyć tej metody do skopiowania częściowej tablicy.


35

Mam wrażenie, że te wszystkie „lepsze sposoby kopiowania tablicy” tak naprawdę nie rozwiążą twojego problemu.

Mówisz

Próbowałem pętli for jak [...], ale to nie działa poprawnie?

Patrząc na tę pętlę, nie ma oczywistego powodu, aby nie działała ... chyba że:

  • jakoś mają ai btablice pomieszane (np ai bodnoszą się do tej samej tablicy), lub
  • Twoja aplikacja jest wielowątkowa, a różne wątki jednocześnie odczytują i aktualizują atablicę.

W obu przypadkach alternatywne sposoby kopiowania nie rozwiążą podstawowego problemu.

Poprawka dla pierwszego scenariusza jest oczywista. W drugim scenariuszu musisz wymyślić jakiś sposób synchronizacji wątków. Klasy atomowe nie pomagają, ponieważ nie mają żadnych konstruktorów kopiowania atomowego ani metod klonowania, ale synchronizacja przy użyciu prymitywnego muteksu wystarczy.

(W twoim pytaniu są wskazówki, które prowadzą mnie do przekonania, że ​​to rzeczywiście jest związane z wątkami; np. Twoje zdanie aciągle się zmienia).


2
zgodził się .. prawdopodobnie prawda.
MeBigFatGuy


9

Wszystkie rozwiązania, które wywołują długość z tablicy, dodaj kod nadmiarowy zerowy warcaby rozważ przykład:

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();
    }
}

Zalecam, aby nie wymyślać koła i używać klasy użytkowej, w której wszystkie niezbędne kontrole zostały już wykonane. Rozważ ArrayUtils z apache commons. Twój kod staje się krótszy:

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

Fotografia Apache można znaleźć tam


8

Możesz także użyć Arrays.copyOfRange.

Przykład :

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]
}

Ta metoda jest podobna Arrays.copyOf, ale jest bardziej elastyczna. Oba używają System.arraycopypod maską.

Zobacz :


3

W przypadku bezpiecznej dla tablicy kopii tablicy można użyć opcjonalnej Object.clone()metody opisanej w tej odpowiedzi .

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

Pomimo tego, że to rozwiązanie jest przesadzone, wprowadza także marnotrawstwo pamięci, a jeśli tablica zawiera sekret (np. Tablica bajtów z hasłem), wprowadza także wady bezpieczeństwa, ponieważ obiekty pośrednie będą znajdować się na stercie, aż do momentu wyrzucenia elementów bezużytecznych atakującym.
Weltraumschaf

1
Nie zgadzam się, że tablica będzie na stosie specjalnie dla tego konstruktu. Rzeczywiście wywołuje klonowanie tylko wtedy, gdy jest to potrzebne, a Optionalobiekt jest tylko pustym obiektem z odniesieniem do istniejącej tablicy. Jeśli chodzi o wpływ na wydajność, powiedziałbym, że przedwczesne jest twierdzenie, że jest to wpływ, ponieważ ten typ konstrukcji jest dobrym kandydatem do wbudowania w JVM, a następnie nie większy wpływ niż inne metody. To kwestia stylu (programowanie funkcjonalne kontra programowanie proceduralne, ale nie tylko), aby uznać to za bardziej skomplikowane lub nie.
Nicolas Henneaux

3

Jeśli trzeba pracować z surowych tablic, a nie ArrayListwtedy Arraysma to, czego potrzebujesz. Jeśli spojrzysz na kod źródłowy, są to absolutnie najlepsze sposoby na uzyskanie kopii tablicy. Mają sporo programowania obronnego, ponieważ System.arraycopy()metoda rzuca wiele niesprawdzonych wyjątków, jeśli podajesz jej nielogiczne parametry.

Możesz użyć jednego, Arrays.copyOf()który skopiuje od pierwszego do Nthelementu do nowej krótszej tablicy.

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

Kopiuje określoną tablicę, obcinając lub dopełniając wartości zerowe (jeśli to konieczne), aby kopia miała określoną długość. Dla wszystkich indeksów, które są poprawne zarówno w oryginalnej tablicy, jak i kopii, dwie tablice będą zawierać identyczne wartości. W przypadku wszystkich indeksów, które są poprawne w kopii, ale nie w oryginale, kopia będzie zawierać null. Takie indeksy będą istnieć wtedy i tylko wtedy, gdy określona długość jest większa niż oryginalnej tablicy. Wynikowa tablica jest dokładnie tej samej klasy co tablica oryginalna.

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    }

lub Arrays.copyOfRange()zrobi lewę:

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

Kopiuje określony zakres określonej tablicy do nowej tablicy. Początkowy indeks zakresu (od) musi mieścić się w przedziale od zera do oryginalnej długości. Wartość w oryginale [from] jest umieszczana w początkowym elemencie kopii (chyba że od == original.length lub od == do). Wartości z kolejnych elementów w oryginalnej tablicy są umieszczane w kolejnych elementach w kopii. Końcowy indeks zakresu (do), który musi być większy lub równy od, może być większy niż oryginał. Długość, w którym to przypadku wartość null jest umieszczana we wszystkich elementach kopii, których indeks jest większy lub równy oryginałowi. długość - od. Długość zwracanej tablicy będzie wynosić do - od. Wynikowa tablica jest dokładnie tej samej klasy co tablica oryginalna.

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    }

Jak widać, obie są po prostu funkcjami otoki System.arraycopyz logiką obronną, że to, co próbujesz zrobić, jest prawidłowe.

System.arraycopy jest absolutnie najszybszym sposobem kopiowania tablic.


0

Miałem podobny problem z tablicami 2D i skończyłem tutaj. Kopiowałem główną tablicę i zmieniałem wartości wewnętrznych tablic i byłem zaskoczony, gdy wartości zmieniły się w obu kopiach. Zasadniczo obie kopie były niezależne, ale zawierały odniesienia do tych samych wewnętrznych tablic i musiałem wykonać szereg kopii wewnętrznych tablic, aby uzyskać to, czego chciałem.

Prawdopodobnie nie jest to problem PO, ale mam nadzieję, że nadal może być pomocny.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.