Introduktion til Spliterator i Java

1. Oversigt

Det Spliterator interface, introduceret i Java 8, kan være bruges til at krydse og opdele sekvenser. Det er et basisværktøj til Strømme, især parallelle.

I denne artikel dækker vi brugen, karakteristika, metoder og hvordan vi opretter vores egne brugerdefinerede implementeringer.

2. Spliterator API

2.1. prøv Avance

Dette er den vigtigste metode, der bruges til at gå gennem en sekvens. Metoden tager en Forbruger der bruges til at forbruge elementer af Spliterator en efter en sekventielt og vender tilbage falsk hvis der ikke er nogen elementer, der skal krydses.

Her ser vi på, hvordan man bruger det til at krydse og partitionere elementer.

Lad os først antage, at vi har en ArrayList med 35000 artikler og det Artikel klasse er defineret som:

public class Article {private List listOfAuthors; privat int id; privat strengnavn; // standard konstruktører / getters / setters}

Lad os nu implementere en opgave, der behandler listen over artikler og tilføjer et suffiks af “- udgivet af Baeldung ” til hvert artikelnavn:

offentlig strengopkald () {int nuværende = 0; mens (spliterator.tryAdvance (a -> a.setName (article.getName () .concat ("- udgivet af Baeldung")))) {nuværende ++; } returner Thread.currentThread (). getName () + ":" + nuværende; }

Bemærk, at denne opgave udlæser antallet af behandlede artikler, når den er færdig med udførelsen.

Et andet vigtigt punkt er, at vi brugte tryAdvance () metode til at behandle det næste element.

2.2. prøvSplit

Lad os derefter splitte Splitterere (deraf navnet) og behandle partitioner uafhængigt.

Det prøvSplit metode forsøger at opdele det i to dele. Derefter behandler opkaldsproceselementerne og endelig den returnerede instans de andre, så de to kan behandles parallelt.

Lad os først generere vores liste:

offentlig statisk liste generere Elementer () {returnere Stream.generate (() -> ny artikel ("Java")) .limit (35000) .collect (Collectors.toList ()); }

Dernæst får vi vores Spliterator eksempel ved hjælp af spliterator () metode. Så anvender vi vores trySplit () metode:

@Test offentlig ugyldighed givenSpliterator_whenAppliedToAListOfArticle_thenSplittedInHalf () {Spliterator split1 = Executor.generateElements (). Spliterator (); Spliterator split2 = split1.trySplit (); assertThat (new Task (split1) .call ()) .containsSequence (Executor.generateElements (). size () / 2 + ""); assertThat (new Task (split2) .call ()) .containsSequence (Executor.generateElements (). size () / 2 + ""); }

Opdelingsprocessen fungerede efter hensigten og delte optegnelserne ens.

2.3. estimeret størrelse

Det estimeret størrelse metode giver os et anslået antal elementer:

LOG.info ("Størrelse:" + split1.estimateSize ());

Dette vil sende:

Størrelse: 17500

2.4. har egenskaber

Denne API kontrollerer, om de givne egenskaber matcher egenskaberne for Spliterator. Så hvis vi påberåber os fremgangsmåden ovenfor, vil output være en int repræsentation af disse egenskaber:

LOG.info ("Karakteristika:" + split1.karakteristika ());
Egenskaber: 16464

3. Spliterator Egenskaber

Det har otte forskellige karakteristika, der beskriver dets adfærd. Disse kan bruges som tip til eksterne værktøjer:

  • STØRRELSE hvis det er i stand til at returnere et nøjagtigt antal elementer med estimatstørrelse () metode
  • SORTERET - hvis det gentager sig gennem en sorteret kilde
  • SUBSIDERET - hvis vi deler instansen ved hjælp af en trySplit () metode og få Spliterators, der er STØRRELSE såvel
  • LØBENDE - hvis kilden kan ændres sikkert samtidigt
  • DISTINCT - hvis for hvert par stødte elementer x, y,! x.equals (y)
  • IMMUTABLE - hvis elementer, der holdes af kilde, ikke kan ændres strukturelt
  • IKKE NUL - hvis kilden har nul eller ej
  • BESTILT - hvis det gentager sig over en ordnet sekvens

4. En brugerdefineret Spliterator

4.1. Hvornår skal du tilpasse

Lad os først antage følgende scenarie:

Vi har en artikelklasse med en liste over forfattere og den artikel, der kan have mere end en forfatter. Desuden betragter vi en forfatter, der er relateret til artiklen, hvis hans relaterede artikels id matcher artikel-id.

Vores Forfatter klasse vil se sådan ud:

public class Author {private String name; private int relateredeArticleId; // standard getters, settere & konstruktører}

Dernæst implementerer vi en klasse, der tæller forfattere, mens vi krydser en strøm af forfattere. Derefter klassen vil udføre en reduktion på strømmen.

Lad os se på implementeringen af ​​klassen:

offentlig klasse RelatedAuthorCounter {privat int-tæller; privat boolsk isRelated; // standardkonstruktører / getters offentlige RelatedAuthorCounter akkumulerer (Forfatterforfatter) {if (author.getRelatedArticleId () == 0) {return isRelated? dette: nye RelatedAuthorCounter (counter, true); } ellers {return isRelated? ny RelatedAuthorCounter (tæller + 1, falsk): dette; }} public RelatedAuthorCounter combine (RelatedAuthorCounter RelatedAuthorCounter) {return new RelatedAuthorCounter (counter + RelatedAuthorCounter.counter, RelatedAuthorCounter.isRelated); }}

Hver metode i ovennævnte klasse udfører en bestemt operation, der skal tælles under kørsel.

Først ophobe() metode krydser forfatterne en efter en på en iterativ måde, derefter forene() summerer to tællere ved hjælp af deres værdier. Endelig blev getCounter () returnerer tælleren.

Nu for at teste, hvad vi har gjort indtil videre. Lad os konvertere vores artikels liste over forfattere til en strøm af forfattere:

Stream stream = article.getListOfAuthors (). Stream ();

Og implementere en countAuthor () metode til at udføre reduktionen på strømmen ved hjælp af RelatedAuthorCounter:

private int countAutors (Stream stream) {RelatedAuthorCounter wordCounter = stream.reduce (new RelatedAuthorCounter (0, true), RelatedAuthorCounter :: accumulate, RelatedAuthorCounter :: combine); returner wordCounter.getCounter (); }

Hvis vi brugte en sekventiel strøm, vil output være som forventet “Count = 9”dog opstår problemet, når vi prøver at parallelisere operationen.

Lad os se på følgende testsag:

@Test ugyldigt givetAStreamOfAuthors_whenProcessedInParallel_countProducesWrongOutput () {assertThat (Executor.countAutors (stream.parallel ())). IsGreaterThan (9); }

Der er tilsyneladende noget gået galt - ved at opdele strømmen i en tilfældig position fik en forfatter talt to gange.

4.2. Sådan tilpasses

For at løse dette skal vi implementere en Spliterator der kun opdeler forfattere, når de er beslægtede id og artikelId Tændstikker. Her er implementeringen af ​​vores brugerdefinerede Spliterator:

offentlig klasse RelatedAuthorSpliterator implementerer Spliterator {privat endelig liste liste; AtomicInteger nuværende = nyt AtomicInteger (); // standard konstruktør / getters @ Override offentlig boolsk tryAdvance (Forbrugerhandling) {action.accept (list.get (current.getAndIncrement ())); returner current.get () <list.size (); } @ Override public Spliterator trySplit () {int currentSize = list.size () - current.get (); hvis (currentSize <10) {return null; } for (int splitPos = currentSize / 2 + current.intValue (); splitPos <list.size (); splitPos ++) {if (list.get (splitPos) .getRelatedArticleId () == 0) {Spliterator spliterator = ny RelatedAuthorSpliterator ( list.subList (current.get (), splitPos)); current.set (splitPos); retur spliterator; }} returner null; } @ Override offentligt langt estimatstørrelse () {return list.size () - current.get (); } @ Override public int-karakteristika () {returner CONCURRENT; }}

Ansøger nu countAuthors () metode giver den korrekte output. Følgende kode viser, at:

@Test offentligt ugyldigt givetAStreamOfAuthors_whenProcessedInParallel_countProducesRightOutput () {Stream stream2 = StreamSupport.stream (spliterator, sand); assertThat (Executor.countAutors (stream2.parallel ())). er EqualTo (9); }

Også den skik Spliterator er oprettet fra en liste over forfattere og krydser gennem den ved at holde den aktuelle position.

Lad os diskutere mere detaljeret implementeringen af ​​hver metode:

  • prøv Avance videregiver forfattere til Forbruger på den aktuelle indeksposition og forøger dens position
  • prøvSplit definerer opdelingsmekanismen, i vores tilfælde RelatedAuthorSpliterator oprettes, når id'erne matches, og opdelingen deler listen i to dele
  • estimeret størrelse - er forskellen mellem listestørrelsen og placeringen af ​​den nuværende itererede forfatter
  • egenskaber- returnerer Spliterator egenskaber, i vores tilfælde STØRRELSE som den værdi, der returneres af estimeret størrelse () metoden er nøjagtig; i øvrigt, LØBENDE angiver, at kilden til dette Spliterator kan ændres sikkert af andre tråde

5. Støtte til primitive værdier

Det SpliteratorAPI understøtter primitive værdier inklusive dobbelt, int og lang.

Den eneste forskel mellem at bruge en generisk og en primitiv dedikeret Spliterator er givet Forbruger og typen af Spliterator.

For eksempel når vi har brug for det til en int værdi, vi har brug for at videregive en intConsumer. Desuden er her en liste over primitive dedikerede Splitterere:

  • Af Primitive: overordnet interface til andre primitiver
  • OfInt: A Spliterator specialiseret til int
  • OfDouble: A Spliterator dedikeret til dobbelt
  • OfLong: A Spliterator dedikeret til lang

6. Konklusion

I denne artikel dækkede vi Java 8 Spliterator brug, metoder, egenskaber, opdeling, primitiv support og hvordan man tilpasser den.

Som altid kan den fulde implementering af denne artikel findes på Github.


$config[zx-auto] not found$config[zx-overlay] not found