Anleitung zur java.util.Arrays-Klasse
1. Einführung
In diesem Tutorial werfen wir einen Blick aufjava.util.Arrays, eine Dienstprogrammklasse, die seit Java 1.2 Teil von Java ist.
MitArrays, we können Arrays erstellt, verglichen, sortiert, gesucht, gestreamt und transformiert werden.
2. Erstellen
Schauen wir uns einige Möglichkeiten an, wie wir Arrays erstellen können:copyOf,copyOfRange undfill.
2.1. copyOf undcopyOfRange
UmcopyOfRange verwenden zu können, benötigen wir unser ursprüngliches Array sowie den Anfangsindex (einschließlich) und den Endindex (exklusiv), die wir kopieren möchten:
String[] intro = new String[] { "once", "upon", "a", "time" };
String[] abridgement = Arrays.copyOfRange(storyIntro, 0, 3);
assertArrayEquals(new String[] { "once", "upon", "a" }, abridgement);
assertFalse(Arrays.equals(intro, abridgement));
UmcopyOf zu verwenden, nehmen wirintro and als Zielarraygröße und erhalten ein neues Array dieser Länge zurück:
String[] revised = Arrays.copyOf(intro, 3);
String[] expanded = Arrays.copyOf(intro, 5);
assertArrayEquals(Arrays.copyOfRange(intro, 0, 3), revised);
assertNull(expanded[4]);
Beachten Sie, dasscopyOf pads the array with nulls if our target size is bigger than the original size.
2.2. fill
Eine andere Möglichkeit, ein Array mit fester Länge zu erstellen, istfill, . Dies ist nützlich, wenn wir ein Array möchten, in dem alle Elemente gleich sind:
String[] stutter = new String[3];
Arrays.fill(stutter, "once");
assertTrue(Stream.of(stutter)
.allMatch(el -> "once".equals(el));
Check outsetAll to erstellt ein Array, in dem die Elemente unterschiedlich sind.
Beachten Sie, dass wir das Array vorher selbst instanziieren müssen - im Gegensatz zuString[] filled = Arrays.fill(“once”, 3); -, da diese Funktion eingeführt wurde, bevor Generika in der Sprache verfügbar waren.
3. Vergleichen
Wechseln wir nun zu Methoden zum Vergleichen von Arrays.
3.1. equals anddeepEquals
Wir könnenequals für einen einfachen Array-Vergleich nach Größe und Inhalt verwenden. Wenn wir eine Null als eines der Elemente hinzufügen, schlägt die Inhaltsprüfung fehl:
assertTrue(
Arrays.equals(new String[] { "once", "upon", "a", "time" }, intro));
assertFalse(
Arrays.equals(new String[] { "once", "upon", "a", null }, intro));
Wenn wir verschachtelte oder mehrdimensionale Arrays haben, können wirdeepEquals verwenden, um nicht nur die Elemente der obersten Ebene zu überprüfen, sondern auch die Prüfung rekursiv durchzuführen:
Object[] story = new Object[]
{ intro, new String[] { "chapter one", "chapter two" }, end };
Object[] copy = new Object[]
{ intro, new String[] { "chapter one", "chapter two" }, end };
assertTrue(Arrays.deepEquals(story, copy));
assertFalse(Arrays.equals(story, copy));
Beachten Sie, wiedeepEquals passiert, aberequals fehlschlägt _._
This is because deepEquals ultimately calls itself each time it encounters an array, währendequals will einfach die Referenzen von Sub-Arrays vergleichen.
Dies macht es auch gefährlich, ein Array mit einer Selbstreferenz aufzurufen!
3.2. hashCode anddeepHashCode
Die Implementierung vonhashCode gibt uns den anderen Teil des Vertrags fürequals /hashCode, der für Java-Objekte empfohlen wird. Wir verwendenhashCode, um eine Ganzzahl basierend auf dem Inhalt des Arrays zu berechnen:
Object[] looping = new Object[]{ intro, intro };
int hashBefore = Arrays.hashCode(looping);
int deepHashBefore = Arrays.deepHashCode(looping);
Jetzt setzen wir ein Element des ursprünglichen Arrays auf null und berechnen die Hashwerte neu:
intro[3] = null;
int hashAfter = Arrays.hashCode(looping);
Alternativ überprüftdeepHashCode die verschachtelten Arrays auf übereinstimmende Anzahlen von Elementen und Inhalten. Wenn wir mitdeepHashCode neu berechnen:
int deepHashAfter = Arrays.deepHashCode(looping);
Nun können wir den Unterschied zwischen den beiden Methoden erkennen:
assertEquals(hashAfter, hashBefore);
assertNotEquals(deepHashAfter, deepHashBefore);
deepHashCode is the underlying calculation used when we are working with data structures like HashMap and HashSet on arrays.
4. Sortieren und Suchen
Schauen wir uns als nächstes das Sortieren und Suchen von Arrays an.
4.1. sort
Wenn unsere Elemente entweder Grundelemente sind oderComparable implementieren, können wirsort verwenden, um eine Inline-Sortierung durchzuführen:
String[] sorted = Arrays.copyOf(intro, 4);
Arrays.sort(sorted);
assertArrayEquals(
new String[]{ "a", "once", "time", "upon" },
sorted);
Take care that sort mutates the original reference, weshalb wir hier eine Kopie durchführen.
sort will verwendet einen anderen Algorithmus für verschiedene Array-Elementtypen. Primitive types use a dual-pivot quicksort undObject types use Timsort. Beide haben den Durchschnittsfall vonO(n log(n)) für ein zufällig sortiertes Array.
Ab Java 8 istparallelSort für eine parallele Sortierzusammenführung verfügbar. Es bietet eine gleichzeitige Sortiermethode mit mehrerenArrays.sort -Stasks.
4.2. binarySearch
Die Suche in einem unsortierten Array ist linear, aber wenn wir ein sortiertes Array haben, können wir dies inO(log n) tun, was wir mitbinarySearch: tun können
int exact = Arrays.binarySearch(sorted, "time");
int caseInsensitive = Arrays.binarySearch(sorted, "TiMe", String::compareToIgnoreCase);
assertEquals("time", sorted[exact]);
assertEquals(2, exact);
assertEquals(exact, caseInsensitive);
Wenn wir keinComparator als dritten Parameter angeben, zähltbinarySearch davon, dass unser Elementtyp vom TypComparable ist.
Beachten Sie auch hier, dassif our array isn’t first sorted, then binarySearch won’t work as we expect!
5. Streaming
Wie wir bereits gesehen haben, wurdeArrays in Java 8 aktualisiert und enthält nun Methoden, die die Stream-API verwenden, z. B.parallelSort (oben erwähnt),stream andsetAll.
5.1. stream
stream gibt uns vollen Zugriff auf die Stream-API für unser Array:
Assert.assertEquals(Arrays.stream(intro).count(), 4);
exception.expect(ArrayIndexOutOfBoundsException.class);
Arrays.stream(intro, 2, 1).count();
Wir können inklusive und exklusive Indizes für den Stream bereitstellen, wir sollten jedoch einArrayIndexOutOfBoundsException erwarten, wenn die Indizes nicht in der richtigen Reihenfolge, negativ oder außerhalb des Bereichs liegen.
6. Transformieren
Schließlich bietentoString,asList, undsetAll uns verschiedene Möglichkeiten, Arrays zu transformieren.
6.1. toString unddeepToString
Eine gute Möglichkeit, eine lesbare Version unseres ursprünglichen Arrays zu erhalten, isttoString:
assertEquals("[once, upon, a, time]", Arrays.toString(storyIntro));
Wiederwe must use the deep version to print the contents of nested arrays:
assertEquals(
"[[once, upon, a, time], [chapter one, chapter two], [the, end]]",
Arrays.deepToString(story));
6.2. asList
Die bequemste aller Methoden vonArrays, die wir verwenden können, ist die vonasList.. Wir haben eine einfache Möglichkeit, ein Array in eine Liste umzuwandeln:
List rets = Arrays.asList(storyIntro);
assertTrue(rets.contains("upon"));
assertTrue(rets.contains("time"));
assertEquals(rets.size(), 4);
the returned List will be a fixed length so we won’t be able to add or remove elements.
Beachten Sie auch, dass seltsamerweisejava.util.Arrays has its own ArrayList subclass, which asList returns. Dies kann beim Debuggen sehr trügerisch sein!
6.3. setAll
MitsetAll können wir alle Elemente eines Arrays mit einer funktionalen Schnittstelle festlegen. Die Generator-Implementierung verwendet den Positionsindex als Parameter:
String[] longAgo = new String[4];
Arrays.setAll(longAgo, i -> this.getWord(i));
assertArrayEquals(longAgo, new String[]{"a","long","time","ago"});
Und natürlich ist die Ausnahmebehandlung einer der heikelsten Aspekte bei der Verwendung von Lambdas. Denken Sie also daran, dass hierif the lambda throws an exception, then Java doesn’t define the final state of the array.
7. Paralleles Präfix
Eine weitere neue Methode inArrays, die seit Java 8 eingeführt wurde, istparallelPrefix. MitparallelPrefix können wir jedes Element des Eingabearrays kumulativ bearbeiten.
7.1. parallelPrefix
Wenn der Operator eine Addition wie im folgenden Beispiel durchführt, führt[1, 2, 3, 4] zu[1, 3, 6, 10]:
int[] arr = new int[] { 1, 2, 3, 4};
Arrays.parallelPrefix(arr, (left, right) -> left + right);
assertThat(arr, is(new int[] { 1, 3, 6, 10}));
Außerdem können wir einen Unterbereich für die Operation angeben:
int[] arri = new int[] { 1, 2, 3, 4, 5 };
Arrays.parallelPrefix(arri, 1, 4, (left, right) -> left + right);
assertThat(arri, is(new int[] { 1, 2, 5, 9, 5 }));
Beachten Sie, dass die Methode parallel ausgeführt wird, alsothe cumulative operation should be side-effect-free and associative.
Für eine nicht assoziative Funktion:
int nonassociativeFunc(int left, int right) {
return left + right*left;
}
Die Verwendung vonparallelPrefix würde zu inkonsistenten Ergebnissen führen:
@Test
public void whenPrefixNonAssociative_thenError() {
boolean consistent = true;
Random r = new Random();
for (int k = 0; k < 100_000; k++) {
int[] arrA = r.ints(100, 1, 5).toArray();
int[] arrB = Arrays.copyOf(arrA, arrA.length);
Arrays.parallelPrefix(arrA, this::nonassociativeFunc);
for (int i = 1; i < arrB.length; i++) {
arrB[i] = nonassociativeFunc(arrB[i - 1], arrB[i]);
}
consistent = Arrays.equals(arrA, arrB);
if(!consistent) break;
}
assertFalse(consistent);
}
7.2. Performance
Die parallele Präfixberechnung ist in der Regel effizienter als sequentielle Schleifen, insbesondere bei großen Arrays. Wenn Sie Micro-Benchmark auf einem Intel Xeon-Computer (6 Kerne) mitJMH ausführen, können Sie eine große Leistungsverbesserung feststellen:
Benchmark Mode Cnt Score Error Units
largeArrayLoopSum thrpt 5 9.428 ± 0.075 ops/s
largeArrayParallelPrefixSum thrpt 5 15.235 ± 0.075 ops/s
Benchmark Mode Cnt Score Error Units
largeArrayLoopSum avgt 5 105.825 ± 0.846 ops/s
largeArrayParallelPrefixSum avgt 5 65.676 ± 0.828 ops/s
Hier ist der Benchmark-Code:
@Benchmark
public void largeArrayLoopSum(BigArray bigArray, Blackhole blackhole) {
for (int i = 0; i < ARRAY_SIZE - 1; i++) {
bigArray.data[i + 1] += bigArray.data[i];
}
blackhole.consume(bigArray.data);
}
@Benchmark
public void largeArrayParallelPrefixSum(BigArray bigArray, Blackhole blackhole) {
Arrays.parallelPrefix(bigArray.data, (left, right) -> left + right);
blackhole.consume(bigArray.data);
}
7. Fazit
In diesem Artikel haben wir einige Methoden zum Erstellen, Suchen, Sortieren und Transformieren von Arrays mit der Klassejava.util.Arrayskennengelernt.
Diese Klasse wurde in neueren Java-Versionen um die Methoden zur Erzeugung und Verwendung von Streams inJava 8 und um Nichtübereinstimmungsmethoden inJava 9 erweitert.
Die Quelle für diesen Artikel ist wie immerover on Github.