Introduksjon til Spliterator i Java

1. Oversikt

De Spliterator grensesnitt, introdusert i Java 8, kan være brukes til å krysse og partisjonere sekvenser. Det er et basisverktøy for Strømmer, spesielt parallelle.

I denne artikkelen vil vi dekke bruken, egenskapene, metodene og hvordan vi lager våre egne tilpassede implementeringer.

2. Spliterator API

2.1. prøv avansement

Dette er hovedmetoden som brukes for å gå gjennom en sekvens. Metoden tar en Forbruker som brukes til å konsumere elementer av Spliterator en etter en sekvensielt og kommer tilbake falsk hvis det ikke er noen elementer å krysse.

Her tar vi en titt på hvordan du bruker den til å krysse og partisjonere elementer.

La oss først anta at vi har en ArrayList med 35000 artikler og det Artikkel klasse er definert som:

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

La oss nå implementere en oppgave som behandler listen over artikler og legger til et suffiks av “- utgitt av Baeldung ” til hvert artikkelnavn:

offentlig strenganrop () {int gjeldende = 0; while (spliterator.tryAdvance (a -> a.setName (article.getName () .concat ("- publisert av Baeldung")))) {current ++; } returner Thread.currentThread (). getName () + ":" + gjeldende; }

Legg merke til at denne oppgaven gir ut antall artikler som er behandlet når den er ferdig med utførelsen.

Et annet viktig poeng er at vi brukte tryAdvance () metode for å behandle neste element.

2.2. trySplit

La oss deretter splitte Splitteratorer (derav navnet) og behandle partisjoner uavhengig.

De trySplit metoden prøver å dele den i to deler. Deretter behandler den som ringer og til slutt den returnerte forekomsten de andre, slik at de to kan behandles parallelt.

La oss først generere listen vår:

offentlig statisk liste generereElements () {return Stream.generate (() -> ny artikkel ("Java")) .limit (35000) .collect (Collectors.toList ()); }

Deretter får vi vår Spliterator eksempel bruker spliterator () metode. Så bruker vi vår trySplit () metode:

@Test offentlig ugyldighet 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 + ""); }

Oppdelingsprosessen fungerte etter hensikten og delte postene likt.

2.3. estimert størrelse

De estimert størrelse metoden gir oss et estimert antall elementer:

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

Dette vil sende:

Størrelse: 17500

2.4. har egenskaper

Denne API-en kontrollerer om de gitte egenskapene samsvarer med egenskapene til Spliterator. Så hvis vi påkaller metoden ovenfor, vil utdataene være en int representasjon av disse egenskapene:

LOG.info ("Egenskaper:" + split1.karakteristikker ());
Kjennetegn: 16464

3. Spliterator Kjennetegn

Den har åtte forskjellige egenskaper som beskriver oppførselen. Disse kan brukes som tips for eksterne verktøy:

  • STØRRELSE hvis den er i stand til å returnere et nøyaktig antall elementer med estimatstørrelse () metode
  • SORTERT - hvis det går gjennom en sortert kilde
  • SUBSIZED - hvis vi deler forekomsten ved hjelp av en trySplit () metode og skaffe splitteratorer som er STØRRELSE også
  • SAMTIDIG - hvis kilden kan endres trygt samtidig
  • DISTINKT - hvis for hvert par møter på elementer x, y,! x.equals (y)
  • IMMUTABLE - hvis elementer som holdes av kilde ikke kan endres strukturelt
  • IKKE - hvis kilden har null eller ikke
  • BESTILT - hvis det går over en ordnet sekvens

4. En egendefinert Spliterator

4.1. Når skal du tilpasse

La oss først anta følgende scenario:

Vi har en artikkelklasse med en liste over forfattere, og artikkelen som kan ha mer enn én forfatter. Videre vurderer vi en forfatter relatert til artikkelen hvis den relaterte artikkelens id samsvarer med artikkel-id.

Våre Forfatter klassen vil se slik ut:

offentlig klasse Forfatter {privat strengnavn; private int relaterteArticleId; // standard getters, setters & constructors}

Deretter implementerer vi en klasse for å telle forfattere mens vi krysser en strøm av forfattere. Deretter klassen vil utføre en reduksjon på bekken.

La oss se på implementeringen av klassen:

offentlig klasse RelatedAuthorCounter {private int counter; privat boolsk isRelated; // standardkonstruktører / getters offentlige RelatedAuthorCounter akkumulerer (Forfatterforfatter) {if (author.getRelatedArticleId () == 0) {return isRelated? dette: ny RelatedAuthorCounter (counter, true); } annet {retur erRelatert? ny RelatedAuthorCounter (counter + 1, false): dette; }} public RelatedAuthorCounter combine (RelatedAuthorCounter RelatedAuthorCounter) {return new RelatedAuthorCounter (counter + RelatedAuthorCounter.counter, RelatedAuthorCounter.isRelated); }}

Hver metode i ovennevnte klasse utfører en spesifikk operasjon for å telle mens du krysser.

Først, den akkumulere() metode krysser forfatterne en etter en på en iterativ måte, deretter kombinere() oppsummerer to tellere ved hjelp av deres verdier. Til slutt, getCounter () returnerer disken.

Nå, for å teste hva vi har gjort så langt. La oss konvertere artikkelens liste over forfattere til en strøm av forfattere:

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

Og implementere en countAuthor () metode for å utføre reduksjonen på strømmen ved hjelp av RelatedAuthorCounter:

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

Hvis vi brukte en sekvensiell strøm, vil utdataene være som forventet “Count = 9”imidlertid oppstår problemet når vi prøver å parallellisere operasjonen.

La oss ta en titt på følgende testtilfelle:

@Test ugyldig gittAStreamOfAuthors_whenProcessedInParallel_countProducesWrongOutput () {assertThat (Executor.countAutors (stream.parallel ())). IsGreaterThan (9); }

Tilsynelatende har noe gått galt - å dele strømmen i en tilfeldig posisjon førte til at en forfatter ble talt to ganger.

4.2. Hvordan tilpasse

For å løse dette, må vi implementere en Spliterator som splitter forfattere bare når de er i slekt id og artikkelId fyrstikker. Her er implementeringen av vår skikk Spliterator:

offentlig klasse RelatedAuthorSpliterator implementerer Spliterator {private final List list; AtomicInteger current = new AtomicInteger (); // standardkonstruktør / getters @Override offentlig boolsk tryAdvance (Forbrukerhandling) {action.accept (list.get (current.getAndIncrement ())); return 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 offentlig lang estimeringsstørrelse () {returliste.størrelse () - gjeldende.get (); } @ Override public int egenskaper () {return CONCURRENT; }}

Nå søker countAuthors () metoden vil gi riktig utdata. Følgende kode viser at:

@Test offentlig ugyldighet gittAStreamOfAuthors_whenProcessedInParallel_countProducesRightOutput () {Stream stream2 = StreamSupport.stream (spliterator, true); assertThat (Executor.countAutors (stream2.parallel ())). er EqualTo (9); }

Også skikken Spliterator er opprettet fra en liste over forfattere og går gjennom den ved å beholde den nåværende posisjonen.

La oss diskutere implementeringen av hver metode i flere detaljer:

  • prøv avansert overfører forfattere til Forbruker på gjeldende indeksposisjon og øker sin posisjon
  • trySplit definerer splittemekanismen, i vårt tilfelle, RelatedAuthorSpliterator blir opprettet når ID-er samsvarer, og delingen deler listen i to deler
  • estimert størrelse - er forskjellen mellom listestørrelsen og posisjonen til den gjentatte forfatteren
  • kjennetegn- returnerer Spliterator egenskaper, i vårt tilfelle STØRRELSE som verdien som returneres av estimert størrelse () metoden er nøyaktig; dessuten, SAMTIDIG indikerer at kilden til dette Spliterator kan endres trygt av andre tråder

5. Støtte for primitive verdier

De SpliteratorAPI støtter primitive verdier inkludert dobbelt, int og lang.

Den eneste forskjellen mellom å bruke en generisk og en primitiv dedikert Spliterator er gitt Forbruker og typen Spliterator.

For eksempel når vi trenger det for en int verdi vi trenger for å passere en intConsumer. Videre er her en liste over primitive dedikerte Splitteratorer:

  • AvPrimitive: foreldregrensesnitt for andre primitiver
  • OfInt: A Spliterator spesialisert for int
  • OfDouble: A Spliterator dedikert til dobbelt
  • OfLong: A Spliterator dedikert til lang

6. Konklusjon

I denne artikkelen dekket vi Java 8 Spliterator bruk, metoder, egenskaper, splittelsesprosess, primitiv støtte og hvordan du tilpasser den.

Som alltid finner du fullstendig implementering av denne artikkelen på Github.


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