Was ist effizienter: System.arraycopy oder Arrays.copyOf?

75

Die toArrayMethode in ArrayListBloch verwendet beide System.arraycopyund, Arrays.copyOfum ein Array zu kopieren.

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

Wie kann ich diese beiden Kopiermethoden vergleichen und wann sollte ich welche verwenden?

Sawyer
quelle
1
Was ist "Bloch"? Warum ist das Code-Snippet relevant?
Ciro Santilli 2 冠状 病 六四 事件 2
5
@Ciro Bloch ist der Typ, der die ArrayList-Implementierung geschrieben hat.
Insomniac

Antworten:

105

Der Unterschied besteht darin, dass Arrays.copyOfnicht nur Elemente kopiert, sondern auch ein neues Array erstellt wird. System.arraycopykopiert in ein vorhandenes Array.

Hier ist die Quelle für Arrays.copyOf, wie Sie sehen können, die System.arraycopyintern verwendet wird, um das neue Array zu füllen:

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;
}
Thilo
quelle
2
Es ist offensichtlich ... Arrays.copyOf sollte eine neue Instanz eines Arrays erstellen, da wir keine an dieses übergeben. während wir ein Ziel an System.arraycopy übergeben. In Bezug auf die Geschwindigkeit sollte System.arraycopy natürlich wieder gewinnen, da es sich um eine native Methode handelt, und letztendlich ruft Arrays.copyOf diese Methode auch zum Kopieren eines Arrays auf.
Pravat Panda
4
Die Java-Quelle spielt keine Rolle, da das Intrinsic erstellt wurde.
Maaartinus
auch System.arraycopyhat mehr detaillierte Parameter, können Sie Startindex für beide Arrays angeben.
M. Kazem Akhgary 25.
öffentliche Klasse TestClass {public static void main (String [] args) {Byte a [] = {65, 66, 67, 68, 69, 70, 71, 72, 73, 74}; Systemarraycopy (a, 0, a, 1, a.Länge - 1); System.out.println (neuer String (a)); }}
Rajat
Der obige Code gibt AABCDEFGHI aus, dh, es wird ein neues Array erstellt.
Rajat
39

Während System.arraycopyes nativ implementiert ist und daher 1 schneller als eine Java-Schleife sein könnte, ist es nicht immer so schnell, wie Sie es vielleicht erwarten. Betrachten Sie dieses Beispiel:

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

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

In diesem Fall haben die Arrays foound die barArrays unterschiedliche Basistypen. Daher muss bei der Implementierung von arraycopyder Typ jeder kopierten Referenz überprüft werden, um sicherzustellen, dass es sich tatsächlich um eine Referenz auf eine String-Instanz handelt. Das ist deutlich langsamer als ein einfacher C-Stil memcopydes Array-Inhalts.

Der andere Punkt ist , dass Arrays.copyOfAnwendungen System.arraycopyunter der Motorhaube. Daher System.arraycopyist auf den ersten Blick nicht langsamer 2 als Arrays.copyOf. Aber man kann (aus dem Code zitiert siehe oben ) , dass Arrays.copyOfin einigen Fällen eine Reflexion verwenden Sie das neue Array zu erstellen. Der Leistungsvergleich ist also nicht einfach.

Diese Analyse weist einige Mängel auf.

  1. Wir betrachten den Implementierungscode einer bestimmten Java-Version. Diese Methoden können sich ändern und frühere Annahmen zur Effizienz ungültig machen.

  2. Wir ignorieren die Möglichkeit, dass der JIT-Compiler eine clevere Spezialfalloptimierung für diese Methoden durchführen könnte. Und es passiert anscheinend mit Arrays.copyOf; Siehe Warum ist Arrays.copyOf für kleine Arrays 2-mal schneller als System.arraycopy? . Diese Methode ist in Java-Implementierungen der aktuellen Generation "intrinsisch", was bedeutet, dass der JIT-Compiler ignoriert, was im Java-Quellcode enthalten ist!

In beiden Fällen ist der Unterschied zwischen den beiden Versionen O(1)(dh unabhängig von der Arraygröße) relativ gering. Daher würde ich raten, die Version zu verwenden, mit der Ihr Code am einfachsten zu lesen ist, und sich nur darum zu kümmern, welche schneller ist, wenn die Profilerstellung Ihnen sagt, dass dies wichtig ist.


1 - Es könnte schneller sein, aber es ist auch möglich, dass der JIT-Compiler eine handcodierte Schleife so gut optimiert, dass es keinen Unterschied gibt.

Stephen C.
quelle
1
+1 für den Hinweis auf die Typprüfung, die manchmal stattfinden muss.
Thilo
@StephenC, Gibt es in Bezug auf Ihren ersten Absatz Fälle, in denen System.arrayCopyes tatsächlich langsamer sein könnte ?
Pacerier
@ Pacerier - mir sind keine bekannt. Das ist aber nicht unmöglich.
Stephen C
1
Ein wenig spät bei dieser Antwort, aber wenn man bedenkt, dass System.arrayCopy von Arrays.copyOf referenziert wird (wie in der akzeptierten Antwort erwähnt), müsste ich feststellen, dass es unmöglich ist, dass System.arrayCopy langsamer ist. Dies setzte natürlich voraus, dass es sich nicht um eine benutzerdefinierte Implementierung von JVM mit einer benutzerdefinierten Implementierung der Methoden handelt.
Wayne
@Wayne - Sie machen Annahmen über die Optimierungen, die der JIT-Compiler vornimmt. Es ist möglich, dass der Quellcode / die Bytecodes, die Sie betrachten, vollständig ignoriert werden. Oder dass es das in einer zukünftigen Version von Java tun könnte. Oder dass der Quellcode in einer anderen Version anders sein könnte. Oder dass es eine clevere Optimierung gibt, die bei einem direkten arraycopyAnruf durchgeführt werden kann, die bei einem indirekten Anruf nicht möglich ist. All diese Dinge sind möglich ... wenn nicht plausibel.
Stephen C
14

Wenn Sie eine genaue Kopie eines Arrays wünschen (z. B. wenn Sie eine defensive Kopie erstellen möchten), ist die clone()Methode des Array-Objekts wahrscheinlich die effektivste Methode zum Kopieren eines Arrays :

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

Ich habe mir nicht die Mühe gemacht, die Leistung zu testen, aber es besteht eine gute Chance, ziemlich schnell zu sein, da alles nativ ist (Zuweisung und Kopieren im Anruf), und das Klonen ist eine Art besondere, von JVM gesegnete Art, Objekte zu kopieren (und das ist es auch meistens böse für andere Zwecke) und ist wahrscheinlich in der Lage, einige "Abkürzungen" zu nehmen.

Persönlich würde ich immer noch verwenden, clonewenn es langsamer als jede andere Art des Kopierens wäre, weil es einfacher zu lesen und nahezu unmöglich ist, beim Schreiben Fehler zu machen. System.arrayCopy, auf der anderen Seite...

gustafc
quelle
1
Richtig, aber es System.arrayCopyist auch einheimisch (und es ist dieser Aufgabe gewidmet), während clonees mit vielen Bedingungen zu
kämpfen
@Pacerier "Ich benutze es nicht oft clone... aber wenn ich es tue, benutze ich es auf Arrays."
Gustafc
Ich habe darüber gesprochen , .clone() wenn es auf Arrays verwendet wird ... Auf jeden Fall gibt es immer noch ein Objekt zurück, das dann wieder in ein Array umgewandelt werden muss (= zusätzliche Arbeit).
Pacerier
@Pacerier ist tatsächlich mit dem richtigen Rückgabetyp für alle Array-Typen überladen, sodass Sie kein Casting selbst durchführen müssen und es nicht sicher ist, ob die VM Casts überprüfen muss.
Gustafc
Guter Punkt, Sie sagen also, dass native clone()schneller sein wird als native, System.arrayCopyweil sie clone()sich auch dieser Aufgabe widmet?
Pacerier
13

System.arrayCopy ist viel schneller. Es ist im System, weil es eine direkte Speicherkopie außerhalb von Java Land verwendet. Verwenden Sie es, wenn möglich.

Ry4an Brase
quelle
1
System.arraycopy kopiert jedoch nur in ein vorhandenes Array. Arrays.copyOf erstellt auch das Ausgabearray für Sie.
Thilo
2
Das ist wahr, und solange Sie System.arraycopy unter der Decke verwenden, ist jeder Convenience-Wrapper, den Sie verwenden, nur Soße.
Ry4an Brase
4
Und es gibt Fälle, in denen System.arrayCopy keine direkte Speicherkopie verwendet werden kann.
Stephen C
12

Haben Sie sich die Implementierung von Arrays.copyOf () durch Sun angesehen?

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

Wie zu sehen ist, wird es System.arraycopy()intern verwendet, sodass die Leistung gleich ist.

matsev
quelle
2

System.arrayCopywird nativ implementiert und ist daher schneller als jeder Java-Code. Ich empfehle Ihnen, es zu verwenden.

Humphrey Bogart
quelle
5
Nur weil es nativen Code über JNI verwendet, bedeutet es nicht , dass es schneller sein: javaspecialists.eu/archive/Issue124.html
Dag
2

Anstatt zu debattieren, sind dies die tatsächlichen Ergebnisse. Ihre Wahl hängt natürlich davon ab, wie viele Daten Sie kopieren möchten.

Byte [] Kopierleistungstest

10.000.000 Iterationen 40b array.copyOfRange: 135ms systems.arraycopy: 141ms

10.000.000 Iterationen 1000b array.copyOfRange: 1861ms systems.arraycopy: 2211ms

10.000.000 Iterationen 4000b array.copyOfRange: 6315ms systems.arraycopy: 5251ms

1.000.000 Iterationen 100.000b array.copyOfRange: 15.198 ms systems.arraycopy: 14783 ms

Cyberthreat
quelle
1
Nicht sicher, warum die Abstimmung, dies war relevant und nützlich für die Diskussion.
Cyberthreat
1
Ich würde raten, weil Sie Ihr JDK, OS, HW nicht erwähnt und Ihren Quellcode nicht veröffentlicht haben, um ihn zu validieren.
Hans Wurst
1

Wenn Sie sich sowohl den Quellcode für System.arraycopy () von als auch Array.copyOf () für die Leistung ansehen.

System.arraycopy () ist C-Code, arbeitet direkt auf Ihrem Array, gibt keine Werte zurück und sollte daher viel schneller als Array.copyOf () arbeiten. Wenn Sie jedoch ein neues Array erstellen müssen oder nur den Wert des Kopiervorgangs benötigen, müssen Sie dafür ein neues Array erstellen, die neue Array-Länge festlegen usw. Dies ist also nicht möglich eine Rückgabe System.arraycopy (Quelle, 0, Ziel, 0, Länge).

Für das, was Array.copyOf () dann tun kann, wird ein neues Array für Sie erstellt. Sie können den Rückgabewert von Array.copyOf () einem Array zuweisen oder ihn von einer Methode zurückgeben, da Array.copyOf () Ihnen einen Wert zurückgibt, anstatt direkt auf Ihrem Zielarray zu arbeiten. Somit sieht Ihr Code viel sauberer aus. Aus Gründen der Leistung ist Array.copyOf () jedoch eine generische Typmethode, die nicht im Voraus weiß, mit was sie arbeiten wird. Daher muss Array.newInstance () oder new Object () aufgerufen und dann in den Array-Typ der Eingabe umgewandelt werden.

Also um es zusammenzufassen. Verwenden Sie System.arraycopy () aus Leistungsgründen. Verwenden Sie Array.copyOf () für saubereren Code.

Kevin Ng
quelle
-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));
}
 }
Ambika
quelle
Dies beantwortet die Frage nicht.
Stephen C