Java >> Java tutorial >  >> Tag >> java.util

Vejledning til java.util.Arrays-klassen

1. Introduktion

I denne øvelse tager vi et kig på java.util.Arrays , en hjælpeklasse, der har været en del af Java siden Java 1.2.

Brug af Arrays  vi kan oprette, sammenligne, sortere, søge, streame og transformere arrays.

2. Opretter

Lad os tage et kig på nogle af de måder, vi kan oprette arrays på: copyOf , copyOfRange , og fyld.

2.1. copyOf og copyOfRange

For at bruge copyOfRange , skal vi bruge vores originale array og startindekset (inklusive) og slutindekset (eksklusivt), som vi vil kopiere:

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

Og for at bruge copyOf , vil vi tage intro  og en målarraystørrelse, og vi ville få et nyt array af den længde tilbage:

String[] revised = Arrays.copyOf(intro, 3);
String[] expanded = Arrays.copyOf(intro, 5);

assertArrayEquals(Arrays.copyOfRange(intro, 0, 3), revised);
assertNull(expanded[4]);

Bemærk, at copyOf  udfylder arrayet med null s, hvis vores målstørrelse er større end den oprindelige størrelse.

2.2. fyld

En anden måde, vi kan oprette et array med fast længde, er fill,  hvilket er nyttigt, når vi ønsker et array, hvor alle elementer er ens:

String[] stutter = new String[3];
Arrays.fill(stutter, "once");

assertTrue(Stream.of(stutter)
  .allMatch(el -> "once".equals(el));

Tjek setAll  ud at skabe et array, hvor elementerne er forskellige.

Bemærk, at vi selv skal instansiere arrayet på forhånd – i modsætning til noget som String[] filled =Arrays.fill(“once” , 3); – siden denne funktion blev introduceret før generiske stoffer var tilgængelige på sproget.

3. Sammenligner

Lad os nu skifte til metoder til at sammenligne arrays.

3.1. lig med og deepEquals

Vi kan bruge lig med for enkel array-sammenligning efter størrelse og indhold. Hvis vi tilføjer et nul som et af elementerne, mislykkes indholdskontrollen:

assertTrue(
  Arrays.equals(new String[] { "once", "upon", "a", "time" }, intro));
assertFalse(
  Arrays.equals(new String[] { "once", "upon", "a", null }, intro));

Når vi har indlejrede eller multidimensionelle arrays, kan vi bruge deepEquals for ikke kun at kontrollere elementerne på øverste niveau, men også at udføre kontrollen rekursivt:

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

Bemærk hvor dybE kvalifikationer består, men lig med  mislykkes.

Dette er fordi deepEquals  kalder i sidste ende sig selv, hver gang den støder på et array , mens er lig med  vil blot sammenligne sub-arrays' referencer.

Dette gør det også farligt at kalde på et array med en selvreference!

3.2. hashCode og deepHashCode

Implementeringen af hashCode vil give os den anden del af lig /hashCode kontrakt, der anbefales til Java-objekter. Vi bruger hashCode at beregne et heltal baseret på indholdet af matrixen:

Object[] looping = new Object[]{ intro, intro }; 
int hashBefore = Arrays.hashCode(looping);
int deepHashBefore = Arrays.deepHashCode(looping);

Nu sætter vi et element i det originale array til null og genberegner hash-værdierne:

intro[3] = null;
int hashAfter = Arrays.hashCode(looping);

Alternativt deepHashCode kontrollerer de indlejrede arrays for matchende antal elementer og indhold. Hvis vi genberegner med deepHashCode :

int deepHashAfter = Arrays.deepHashCode(looping);

Nu kan vi se forskellen på de to metoder:

assertEquals(hashAfter, hashBefore);
assertNotEquals(deepHashAfter, deepHashBefore);

deepHashCode er den underliggende beregning, der bruges, når vi arbejder med datastrukturer som HashMap og HashSet på arrays .

4. Sortering og søgning

Lad os derefter tage et kig på sortering og søgning af arrays.

4.1. sortér

Hvis vores elementer enten er primitive, eller de implementerer Sammenlignelige , kan vi bruge sort  for at udføre en in-line sortering:

String[] sorted = Arrays.copyOf(intro, 4);
Arrays.sort(sorted);

assertArrayEquals(
  new String[]{ "a", "once", "time", "upon" }, 
  sorted);

Sørg for den sort muterer den oprindelige reference , hvorfor vi udfører en kopi her.

sortér  vil bruge en anden algoritme til forskellige array-elementtyper. Primitive typer bruger en dual-pivot quicksort og objekttyper bruger Timsort. Begge har det gennemsnitlige tilfælde af O(n log(n))  for et tilfældigt sorteret array.

Fra Java 8, parallelSort  er tilgængelig for en parallel sorteringsfletning. Det tilbyder en samtidig sorteringsmetode ved hjælp af flere Arrays.sort  opgaver.

4.2. binær søgning

At søge i et usorteret array er lineært, men hvis vi har et sorteret array, så kan vi gøre det i O(log n) , hvilket er, hvad vi kan gøre med binarySearch:

int exact = Arrays.binarySearch(sorted, "time");
int caseInsensitive = Arrays.binarySearch(sorted, "TiMe", String::compareToIgnoreCase);

assertEquals("time", sorted[exact]);
assertEquals(2, exact);
assertEquals(exact, caseInsensitive);

Hvis vi ikke leverer en Komparator som en tredje parameter, derefter binarySearch regner med, at vores elementtype er af typen Sammenlignelig .

Og igen, bemærk, at hvis vores array ikke først er sorteret, så binarySearch  vil ikke fungere, som vi forventer!

5. Streaming

Som vi så tidligere, Arrays  blev opdateret i Java 8 til at inkludere metoder, der bruger Stream API såsom parallelSort (nævnt ovenfor), stream  og setAll.

5.1. stream

stream giver os fuld adgang til Stream API for vores array:

Assert.assertEquals(Arrays.stream(intro).count(), 4);

exception.expect(ArrayIndexOutOfBoundsException.class);
Arrays.stream(intro, 2, 1).count();

Vi kan levere inkluderende og eksklusive indekser for streamen, men vi bør forvente en ArrayIndexOutOfBoundsException hvis indeksene er ude af rækkefølge,  negative eller uden for rækkevidde.

6. Transformerer

Til sidst toString, asList, og setAll  giv os et par forskellige måder at transformere arrays på.

6.1. toString og deepToString

En fantastisk måde, vi kan få en læsbar version af vores originale array på, er med toString:

assertEquals("[once, upon, a, time]", Arrays.toString(storyIntro));

Igen vi skal bruge den dybe version til at udskrive indholdet af indlejrede arrays :

assertEquals(
  "[[once, upon, a, time], [chapter one, chapter two], [the, end]]",
  Arrays.deepToString(story));

6.2. asList

Mest bekvemt af alle Arrays metoder som vi kan bruge er asList. Vi har en nem måde at omdanne et array til en liste:

List<String> rets = Arrays.asList(storyIntro);

assertTrue(rets.contains("upon"));
assertTrue(rets.contains("time"));
assertEquals(rets.size(), 4);

Men den returnerede liste vil have en fast længde, så vi ikke kan tilføje eller fjerne elementer .

Bemærk også, at mærkeligt nok java.util.Arrays har sin egen ArrayList underklasse, som asList  returnerer . Dette kan være meget vildledende ved fejlretning!

6.3. sætAlle

Med setAll , kan vi indstille alle elementerne i et array med en funktionel grænseflade. Generatorimplementeringen tager positionsindekset som en parameter:

String[] longAgo = new String[4];
Arrays.setAll(longAgo, i -> this.getWord(i)); 
assertArrayEquals(longAgo, new String[]{"a","long","time","ago"});

Og selvfølgelig er undtagelseshåndtering en af ​​de mere skæve dele ved at bruge lambdaer. Så husk, at her, hvis lambda'en kaster en undtagelse, så definerer Java ikke den endelige tilstand af arrayet.

7. Parallelt præfiks

Endnu en ny metode i Arrays introduceret, da Java 8 er parallelPrefix . Med parallelPrefix , kan vi operere på hvert element i input-arrayet på en kumulativ måde.

7.1. parallelpræfiks

Hvis operatøren udfører addition som i følgende eksempel, [1, 2, 3, 4] vil resultere i [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}));

Vi kan også angive et underområde for operationen:

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

Bemærk, at metoden udføres parallelt, så den kumulative operation bør være bivirkningsfri og associativ .

For en ikke-associativ funktion:

int nonassociativeFunc(int left, int right) {
    return left + right*left;
}

ved hjælp af parallelPrefix ville give inkonsistente resultater:

@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. Ydeevne

Parallel præfiksberegning er normalt mere effektiv end sekventielle sløjfer, især for store arrays. Når vi kører mikrobenchmark på en Intel Xeon-maskine (6 kerner) med JMH, kan vi se en stor ydeevneforbedring:

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

Her er benchmarkkoden:

@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. Konklusion

I denne artikel lærte vi, hvordan nogle metoder til at skabe, søge, sortere og transformere arrays ved hjælp af java.util.Arrays klasse.

Denne klasse er blevet udvidet i nyere Java-udgivelser med inklusion af stream-producerende og -forbrugende metoder i Java 8 og mismatch-metoder i Java 9.

Kilden til denne artikel er som altid ovre på Github.


Java tag