Inleiding tot Spliterator in Java

1. Overzicht

De Spliterator interface, geïntroduceerd in Java 8, kan zijn gebruikt voor het doorlopen en partitioneren van reeksen. Het is een basisprogramma voor Streams, vooral parallelle.

In dit artikel bespreken we het gebruik, de kenmerken, methoden en hoe u onze eigen aangepaste implementaties kunt maken.

2. Spliterator API

2.1. probeer Advance

Dit is de belangrijkste methode die wordt gebruikt om door een reeks te lopen. De methode duurt een Klant dat wordt gebruikt om elementen van de Spliterator een voor een achter elkaar en keert terug false als er geen elementen zijn om doorheen te gaan.

Hier zullen we bekijken hoe u het kunt gebruiken om elementen te doorkruisen en te verdelen.

Laten we eerst aannemen dat we een ArrayList met 35.000 artikelen en zo Artikel klasse wordt gedefinieerd als:

openbare klasse Artikel {privélijst listOfAuthors; privé int id; private String naam; // standard constructors / getters / setters}

Laten we nu een taak implementeren die de lijst met artikelen verwerkt en het achtervoegsel '- uitgegeven door Baeldung " aan elke artikelnaam:

openbare String call () {int current = 0; while (spliterator.tryAdvance (a -> a.setName (article.getName () .concat ("- gepubliceerd door Baeldung")))) {current ++; } return Thread.currentThread (). getName () + ":" + current; }

Merk op dat deze taak het aantal verwerkte artikelen weergeeft wanneer de uitvoering is voltooid.

Een ander belangrijk punt is dat we gebruik hebben gemaakt van probeerAdvance () methode om het volgende element te verwerken.

2.2. trySplit

Laten we vervolgens splitsen Spliteratoren (vandaar de naam) en partities onafhankelijk verwerken.

De trySplit methode probeert het in twee delen te splitsen. Vervolgens verwerkt de aanroeper elementen, en ten slotte zal de geretourneerde instantie de andere verwerken, waardoor de twee parallel kunnen worden verwerkt.

Laten we eerst onze lijst genereren:

openbare statische lijst genererenElements () {retour Stream.generate (() -> nieuw artikel ("Java")) .limit (35000) .collect (Collectors.toList ()); }

Vervolgens krijgen we onze Spliterator instantie met behulp van de spliterator () methode. Dan passen we onze toe trySplit () methode:

@Test openbare leegte gegevenSpliterator_whenAppliedToAListOfArticle_thenSplittedInHalf () {Spliterator split1 = Executor.generateElements (). Spliterator (); Spliterator split2 = split1.trySplit (); assertThat (nieuwe taak (split1) .call ()) .containsSequence (Executor.generateElements (). size () / 2 + ""); assertThat (nieuwe taak (split2) .call ()) .containsSequence (Executor.generateElements (). size () / 2 + ""); }

Het splitsingsproces werkte zoals bedoeld en verdeelde de records gelijkmatig.

2.3. geschatte grootte

De geschatte grootte methode geeft ons een geschat aantal elementen:

LOG.info ("Grootte:" + split1.estimateSize ());

Dit levert het volgende op:

Grootte: 17500

2.4. hasCharacteristics

Deze API controleert of de gegeven kenmerken overeenkomen met de eigenschappen van het Spliterator. Als we vervolgens de bovenstaande methode aanroepen, is de uitvoer een int weergave van die kenmerken:

LOG.info ("Characteristics:" + split1.characteristics ());
Kenmerken: 16464

3. Spliterator Kenmerken

Het heeft acht verschillende kenmerken die zijn gedrag beschrijven. Die kunnen worden gebruikt als hints voor externe tools:

  • MAAT als het in staat is om een ​​exact aantal elementen te retourneren met de schattingSize () methode
  • GESORTEERD - als het door een gesorteerde bron loopt
  • GESLOTEN - als we de instantie splitsen met een trySplit () methode en verkrijg Spliterators die zijn MAAT ook
  • CONCURRENT - als de bron gelijktijdig veilig kan worden gewijzigd
  • DISTINCT - als voor elk paar gevonden elementen x, y,! x. is gelijk aan (y)
  • ONVERANDERLIJK - als elementen die door de bron worden bewaard, niet structureel kunnen worden gewijzigd
  • NONNULL - als de bron null-waarden bevat of niet
  • BESTELD - bij iteratie over een geordende reeks

4. Een gewoonte Spliterator

4.1. Wanneer aanpassen

Laten we eerst het volgende scenario aannemen:

We hebben een artikelklasse met een lijst van auteurs, en het artikel kan meer dan één auteur hebben. Bovendien beschouwen we een auteur gerelateerd aan het artikel als de ID van zijn gerelateerde artikel overeenkomt met de artikel-ID.

Onze Schrijver klasse zal er als volgt uitzien:

public class Auteur {private String naam; privé int relatedArticleId; // standaard getters, setters & constructeurs}

Vervolgens implementeren we een les om auteurs te tellen terwijl we een stroom auteurs doorlopen. Dan de klas zal een reductie uitvoeren op de stroom.

Laten we eens kijken naar de implementatie van de klas:

openbare klasse RelatedAuthorCounter {privé int teller; private boolean isRelated; // standard constructors / getters public RelatedAuthorCounter accumuleren (Auteur auteur) {if (author.getRelatedArticleId () == 0) {return isRelated? this: new RelatedAuthorCounter (counter, true); } else {return isRelated? nieuwe RelatedAuthorCounter (counter + 1, false): this; }} public RelatedAuthorCounter combineren (RelatedAuthorCounter RelatedAuthorCounter) {retourneer nieuwe RelatedAuthorCounter (counter + RelatedAuthorCounter.counter, RelatedAuthorCounter.isRelated); }}

Elke methode in de bovenstaande klasse voert een specifieke bewerking uit om te tellen tijdens het doorlopen.

Eerst de accumuleren() methode doorlopen de auteurs een voor een op een iteratieve manier, dan combineren() telt twee tellers op met hun waarden. eindelijk, de getCounter () geeft de teller terug.

Nu, om te testen wat we tot nu toe hebben gedaan. Laten we de lijst met auteurs van ons artikel omzetten in een stroom auteurs:

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

En implementeer een countAuthor () methode om de reductie op de stream uit te voeren met RelatedAuthorCounter:

private int countAutors (Stream stream) {RelatedAuthorCounter wordCounter = stream.reduce (nieuwe RelatedAuthorCounter (0, true), RelatedAuthorCounter :: accumulate, RelatedAuthorCounter :: combineren); retourneer wordCounter.getCounter (); }

Als we een sequentiële stream hebben gebruikt, is de uitvoer zoals verwacht "Count = 9"Het probleem doet zich echter voor wanneer we proberen de operatie parallel te zetten.

Laten we eens kijken naar de volgende testcase:

@Test ongeldig gegevenAStreamOfAuthors_whenProcessedInParallel_countProducesWrongOutput () {assertThat (Executor.countAutors (stream.parallel ())). IsGreaterThan (9); }

Blijkbaar is er iets misgegaan - het splitsen van de stream op een willekeurige positie zorgde ervoor dat een auteur tweemaal werd geteld.

4.2. Hoe aan te passen

Om dit op te lossen, moeten we implementeren een Spliterator die auteurs alleen splitst als ze gerelateerd zijn ID kaart en artikel-ID wedstrijden. Hier is de implementatie van onze gewoonte Spliterator:

openbare klasse RelatedAuthorSpliterator implementeert Spliterator {privé definitieve lijstlijst; AtomicInteger current = nieuwe AtomicInteger (); // standard constructor / getters @Override public boolean tryAdvance (Consumer action) {action.accept (list.get (current.getAndIncrement ())); return current.get () <list.size (); } @Override publieke spliterator trySplit () {int currentSize = list.size () - current.get (); if (currentSize <10) {return null; } for (int splitPos = currentSize / 2 + current.intValue (); splitPos <list.size (); splitPos ++) {if (list.get (splitPos) .getRelatedArticleId () == 0) {Spliterator spliterator = nieuwe RelatedAuthorSpliterator ( list.subList (current.get (), splitPos)); current.set (splitPos); terugkeer spliterator; }} retourneer null; } @Override openbare lange schattingSize () {return list.size () - current.get (); } @Override public int kenmerken () {return CONCURRENT; }}

Nu solliciteren countAuthors () methode geeft de juiste uitvoer. De volgende code laat zien dat:

@ Test openbare ongeldig gegevenAStreamOfAuthors_whenProcessedInParallel_countProducesRightOutput () {Stream stream2 = StreamSupport.stream (spliterator, true); assertThat (Executor.countAutors (stream2.parallel ())). isEqualTo (9); }

Ook de gewoonte Spliterator wordt gemaakt op basis van een lijst met auteurs en doorloopt deze door de huidige positie vast te houden.

Laten we de implementatie van elke methode nader bespreken:

  • probeer Advance geeft auteurs door aan de Klant op de huidige indexpositie en verhoogt zijn positie
  • trySplit definieert het splitsingsmechanisme, in ons geval de RelatedAuthorSpliterator wordt gemaakt wanneer id's overeenkomen, en de opsplitsing verdeelt de lijst in twee delen
  • geschatte grootte - is het verschil tussen de lijstgrootte en de positie van de momenteel herhaalde auteur
  • kenmerken- geeft de Spliterator kenmerken, in ons geval MAAT als de waarde die wordt geretourneerd door de geschatte grootte () methode is exact; bovendien, CONCURRENT geeft aan dat de bron hiervan Spliterator kan veilig worden gewijzigd door andere threads

5. Ondersteuning voor primitieve waarden

De SpliteratorAPI ondersteunt primitieve waarden, waaronder dubbele, int en lang.

Het enige verschil tussen het gebruik van een generiek en een primitief dedicated Spliterator is het gegeven Klant en het type Spliterator.

Als we het bijvoorbeeld nodig hebben voor een int waarde die we nodig hebben om een intConsumer. Verder is hier een lijst met primitieve dedicated Spliteratoren:

  • OfPrimitive: ouderinterface voor andere primitieven
  • OfInt: EEN Spliterator gespecialiseerd voor int
  • OfDouble: EEN Spliterator toegewijd aan dubbele
  • OfLong: EEN Spliterator toegewijd aan lang

6. Conclusie

In dit artikel hebben we Java 8 behandeld Spliterator gebruik, methoden, kenmerken, splitsingsproces, primitieve ondersteuning en hoe deze aan te passen.

Zoals altijd is de volledige implementatie van dit artikel te vinden op Github.