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.