Leitfaden für die Klasse java.util.Arrays

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.