Vejledning til java.util.Arrays-klassen

1. Introduktion

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

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

2. Oprettelse

Lad os se på nogle af de måder, vi kan oprette arrays på: copyOf, copyOfRangeog fylde.

2.1. copyOf og copyOfRange

At bruge copyOfRange, vi har brug for vores originale array og startindekset (inklusive) og slutindekset (eksklusivt), som vi vil kopiere:

String [] intro = new String [] {"once", "upon", "a", "time"}; Streng [] forkortelse = Arrays.copyOfRange (storyIntro, 0, 3); assertArrayEquals (ny streng [] {"en gang", "ved", "a"}, forkortelse); assertFalse (Arrays.equals (intro, forkortelse));

Og at bruge copyOf, ville vi tage intro og en målgruppestørrelse, og vi får tilbage en ny matrix af den længde:

Streng [] revideret = Arrays.copyOf (intro, 3); Streng [] udvidet = Arrays.copyOf (intro, 5); assertArrayEquals (Arrays.copyOfRange (intro, 0, 3), revideret); assertNull (udvidet [4]);

Noter det copyOf padser arrayet med nuls hvis vores målstørrelse er større end den oprindelige størrelse.

2.2. fylde

En anden måde, vi kan oprette et array med fast længde er fylde, hvilket er nyttigt, når vi vil have en matrix, hvor alle elementer er ens:

String [] stutter = ny streng [3]; Arrays.fill (stammen, "en gang"); assertTrue (Stream.of (stutter) .allMatch (el -> "en gang". ligesom (el));

Tjek ud sætAlle for at oprette en matrix, hvor elementerne er forskellige.

Bemærk, at vi er nødt til at instantiere arrayet på forhånd - i modsætning til noget lignende Streng [] fyldt = Arrays.fill (“en gang”, 3);- Siden denne funktion blev introduceret, før generiske stoffer var tilgængelige på sproget.

3. Sammenligning

Lad os nu skifte til metoder til sammenligning af arrays.

3.1. lige med og deepEquals

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

assertTrue (Arrays.equals (ny streng [] {"en gang", "ved", "a", "tid"}, intro)); assertFalse (Arrays.equals (ny streng [] {"en gang", "på", "a", null}, intro));

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

Objekt [] historie = nyt Objekt [] {intro, ny streng [] {"kapitel et", "kapitel to"}, slut}; Objekt [] copy = nyt objekt [] {intro, ny streng [] {"kapitel et", "kapitel to"}, slut}; assertTrue (Arrays.deepEquals (historie, kopi)); assertFalse (Arrays.equals (historie, kopi));

Bemærk hvordan dybkvals passerer men lige med mislykkes.

Dette er fordi deepEquals i sidste ende kalder sig selv hver gang den møder en matrix, mens lige med vil blot sammenligne underarrayers referencer.

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

3.2. hashCode og deepHashCode

Gennemførelsen af hashCode vil give os den anden del af lige med/hashCode kontrakt, der anbefales til Java-objekter. Vi bruger hashCode at beregne et heltal baseret på indholdet af arrayet:

Objekt [] looping = nyt objekt [] {intro, intro}; int hashBefore = Arrays.hashCode (looping); int deepHashBefore = Arrays.deepHashCode (looping);

Nu indstiller vi et element i det originale array til null og beregner hash-værdierne igen:

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

Alternativt deepHashCode kontrollerer de indlejrede arrays for at matche antallet af elementer og indhold. Hvis vi genberegner med deepHashCode:

int deepHashAfter = Arrays.deepHashCode (looping);

Nu kan vi se forskellen i de to metoder:

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

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

4. Sortering og søgning

Lad os derefter se på sortering og søgning af arrays.

4.1. sortere

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

Streng [] sorteret = Arrays.copyOf (intro, 4); Arrays.sort (sorteret); assertArrayEquals (ny streng [] {"a", "en gang", "tid", "ved"}, sorteret);

Pas på det sortere muterer den oprindelige reference, hvorfor vi udfører en kopi her.

sortere bruger en anden algoritme til forskellige matrixelementtyper. Primitive typer bruger en dobbelt drejelig quicksort og Objekt typer bruger Timsort. Begge har det gennemsnitlige tilfælde af O (n log (n)) til et tilfældigt sorteret array.

Fra og med 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ærsøgning

Søgning i et usorteret array er lineært, men hvis vi har et sorteret array, kan vi gøre det i O (log n), hvilket er hvad vi kan gøre med binærsøgning:

int exact = Arrays.binarySearch (sorteret, "tid"); int caseInsensitive = Arrays.binarySearch (sorteret, "TiMe", String :: CompareToIgnoreCase); assertEquals ("tid", sorteret [eksakt]); assertEquals (2, nøjagtig); assertEquals (eksakt, store og små bogstaver)

Hvis vi ikke giver en Komparator som en tredje parameter, derefter binærsøgning regner med, at vores elementtype er af typen Sammenlignelig.

Og igen, bemærk det hvis vores array ikke først sorteres, så binærsøgning fungerer ikke som vi forventer!

5. Streaming

Som vi så tidligere, Arrays blev opdateret i Java 8 til at omfatte metoder, der bruger Stream API, f.eks parallelSort (nævnt ovenfor), strøm og sætAlle.

5.1. strøm

strøm giver os fuld adgang til Stream API til 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 til strømmen, men vi kan forvente en ArrayIndexOutOfBoundsException hvis indekserne er ude af drift, negative eller uden for rækkevidde.

6. Transformering

Langt om længe, toString,asListe, og sætAlle give os et par forskellige måder at transformere arrays på.

6.1. toString og deepToString

En god måde, hvorpå vi kan få en læsbar version af vores originale array 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], [kapitel et, kapitel to], [the, end]]", Arrays.deepToString (historie));

6.2. asListe

Mest bekvemt af alle de Arrays metoder til at bruge er asListe. Vi har en nem måde at gøre en matrix til en liste:

Liste rets = Arrays.asList (storyIntro); assertTrue (rets.contains ("upon")); assertTrue (rets.contains ("tid")); assertEquals (rets.size (), 4);

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

Vær også opmærksom på, at java.util.Arrays har sin egen ArrayList underklasse, som asListe vender tilbage. Dette kan være meget vildledende ved debugging!

6.3. sætAlle

Med sætAlle, kan vi indstille alle elementerne i en matrix med en funktionel grænseflade. Generatorimplementeringen tager positionsindekset som en parameter:

Streng [] longAgo = ny streng [4]; Arrays.setAll (longAgo, i -> this.getWord (i)); assertArrayEquals (longAgo, ny streng [] {"a", "lang", "tid", "siden"});

Og selvfølgelig er undtagelseshåndtering en af ​​de mere terningfulde dele af brugen af ​​lambdas. Så husk det her, hvis lambda kaster en undtagelse, definerer Java ikke den endelige tilstand for arrayet.

7. Parallelt præfiks

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

7.1. parallelPrefix

Hvis operatøren udfører tilføjelse som i den følgende prøve, [1, 2, 3, 4] vil medføre [1, 3, 6, 10]:

int [] arr = ny int [] {1, 2, 3, 4}; Arrays.parallelPrefix (arr, (venstre, højre) -> venstre + højre); assertThat (arr, er (ny int [] {1, 3, 6, 10}));

Vi kan også specificere en underrange til operationen:

int [] arri = ny int [] {1, 2, 3, 4, 5}; Arrays.parallelPrefix (arri, 1, 4, (venstre, højre) -> venstre + højre); assertThat (arri, er (ny int [] {1, 2, 5, 9, 5}));

Bemærk, at metoden udføres parallelt, så den kumulative operation skal være fri for bivirkninger og associerende.

For en ikke-associerende funktion:

int nonassociativeFunc (int venstre, int højre) {tilbage venstre + højre * venstre; }

ved brug af parallelPrefix ville give inkonsekvente resultater:

@Test offentlig ugyldigt nårPrefixNonAssociative_thenError () {boolean consistent = true; Tilfældig r = ny tilfældig (); 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, dette :: nonassociativeFunc); for (int i = 1; i <arrB.length; i ++) {arrB [i] = nonassociativeFunc (arrB [i - 1], arrB [i]); } konsekvent = Arrays.equals (arrA, arrB); hvis (! konsekvent) pause; } hævder Falsk (konsekvent); }

7.2. Ydeevne

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

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 largeArrayParallgtPrefixS s

Her er benchmark-koden:

@Benchmark public void largeArrayLoopSum (BigArray bigArray, Blackhole blackhole) {for (int i = 0; jeg venstre + højre); blackhole.consume (bigArray.data); }

7. Konklusion

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

Denne klasse er blevet udvidet i nyere Java-udgivelser med inkludering af strømproducerende og forbrugende metoder i Java 8 og mismatch-metoder i Java 9.

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