Uvod u Spliterator na Javi

1. Pregled

The Spliterator sučelje, uvedeno u Javi 8, može biti koristi se za kretanje i dijeljenje sekvenci. To je osnovni uslužni program za Potoci, posebno paralelne.

U ovom ćemo članku pokriti njegovu upotrebu, karakteristike, metode i kako stvoriti vlastite prilagođene implementacije.

2. Spliterator API

2.1. tryAdvance

Ovo je glavna metoda koja se koristi za koračanje kroz niz. Metoda traje a Potrošač koja se koristi za konzumiranje elemenata Spliterator jedan po jedan uzastopno i vraća se lažno ako nema elemenata koji se prelaze.

Ovdje ćemo pogledati kako ga koristiti za prelazak i pregrađivanje elemenata.

Prvo, pretpostavimo da imamo ArrayList s 35000 članaka i to Članak klasa se definira kao:

članak javne klase {privatni popis listOfAuthors; privatni int id; privatni naziv niza; // standardni konstruktori / getteri / postavljači}

Sada, provedimo zadatak koji obrađuje popis članaka i dodaje sufiks „- objavio Baeldung ” za svaki naziv članka:

javni String poziv () {int current = 0; while (spliterator.tryAdvance (a -> a.setName (article.getName () .concat ("- objavio Baeldung")))) {trenutni ++; } return Thread.currentThread (). getName () + ":" + trenutno; }

Primijetite da ovaj zadatak daje broj obrađenih članaka kada završi izvršenje.

Druga ključna stvar je da smo koristili tryAdvance () metoda za obradu sljedećeg elementa.

2.2. trySplit

Dalje, podijelimo se Spliteratori (otuda i naziv) i neovisno obrađujte particije.

The trySplit metoda pokušava podijeliti na dva dijela. Zatim će elementi obrade pozivatelja i na kraju vraćena instanca obraditi ostale, omogućujući paralelnu obradu ta dva.

Prvo generirajmo svoj popis:

javni statični popis generiraElements () {return Stream.generate (() -> novi članak ("Java")) .limit (35000) .collect (Collectors.toList ()); }

Dalje dobivamo svoj Spliterator primjer pomoću spliterator () metoda. Tada primjenjujemo svoje trySplit () metoda:

@Test javna praznina givenSpliterator_whenAppliedToAListOfArticle_thenSplittedInHalf () {Spliterator split1 = Executor.generateElements (). Spliterator (); Spliterator split2 = split1.trySplit (); assertThat (novi zadatak (split1) .call ()) .containsSequence (Executor.generateElements (). size () / 2 + ""); assertThat (novi zadatak (split2) .call ()) .containsSequence (Executor.generateElements (). size () / 2 + ""); }

Proces cijepanja djelovao je kako je predviđeno i zapisi su se podijelili podjednako.

2.3. procijenjenaVeličina

The procijenjenaVeličina metoda daje nam procijenjeni broj elemenata:

LOG.info ("Veličina:" + split1.estimateSize ());

Ovo će dati izlaz:

Veličina: 17500

2.4. imaKarakteristike

Ovaj API provjerava odgovaraju li zadane karakteristike svojstvima Spliterator. Tada, ako pozivamo gornju metodu, izlaz će biti int prikaz tih karakteristika:

LOG.info ("Karakteristike:" + split1.karakteristike ());
Karakteristike: 16464

3. Spliterator Karakteristike

Ima osam različitih karakteristika koje opisuju njegovo ponašanje. Oni se mogu koristiti kao savjeti za vanjske alate:

  • VELIČINA ako je sposoban vratiti točan broj elemenata pomoću procjenaSize () metoda
  • RASPOREDENO - ako se ponavlja kroz razvrstani izvor
  • SUBVIZIRANO - ako instancu podijelimo pomoću a trySplit () metodom i dobiti spliteratore koji su VELIČINA također
  • HITNO - ako se izvor može istovremeno istodobno sigurno mijenjati
  • RAZLIKUJ - ako za svaki par pronađenih elemenata x, y,! x.equals (y)
  • NEPOKRENO - ako se elementi koje drži izvor ne mogu strukturno izmijeniti
  • NONULUL - ako izvor sadrži nule ili ne
  • NARUČENO - ako se ponavlja kroz uređeni slijed

4. Običaj Spliterator

4.1. Kada prilagoditi

Prvo, pretpostavimo sljedeći scenarij:

Imamo klasu članaka s popisom autora i članak koji može imati više autora. Nadalje, smatramo da je autor povezan sa člankom ako se ID povezanog članka podudara s ID-om članka.

Naše Autor razred će izgledati ovako:

autor javne klase {naziv privatnog niza; private int relatedArticleId; // standardni getteri, postavljači i konstruktori}

Dalje, implementirat ćemo razred za brojanje autora tijekom prolaska kroz tok autora. Zatim razred će izvršiti redukciju na potoku.

Pogledajmo implementaciju nastave:

javna klasa RelatedAuthorCounter {private int counter; private boolean isRelated; // standardni konstruktori / getteri javni RelatedAuthorCounter akumuliraju (autor autora) {if (author.getRelatedArticleId () == 0) {return isRelated? ovo: novi RelatedAuthorCounter (brojač, točno); } else {return isRelated? novi RelatedAuthorCounter (brojač + 1, netačno): this; }} javna kombinacija RelatedAuthorCounter (RelatedAuthorCounter RelatedAuthorCounter) {return new RelatedAuthorCounter (counter + RelatedAuthorCounter.counter, RelatedAuthorCounter.isRelated); }}

Svaka metoda u gornjoj klasi izvodi određenu operaciju za brojanje tijekom putovanja.

Prvo, akumulirati() metoda prelaze autore jednog po jednog na iterativni način, onda kombinirati() zbraja dva brojača koristeći njihove vrijednosti. Napokon, getCounter () vraća brojač.

Sada, da testiramo što smo do sada učinili. Pretvorimo popis autora iz članka u tok autora:

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

I provesti a countAuthor () metoda za izvođenje smanjenja na toku koristeći RelatedAuthorCounter:

private int countAutors (stream stream) {RelatedAuthorCounter wordCounter = stream.reduce (novi RelatedAuthorCounter (0, istina), RelatedAuthorCounter :: akumulirati, RelatedAuthorCounter :: kombinirati); vratiti wordCounter.getCounter (); }

Ako smo koristili sekvencijalni tok, izlaz će biti onakav kakav se očekivao "Broji = 9", međutim, problem nastaje kada pokušavamo paralelizirati operaciju.

Pogledajmo sljedeći test:

@Test void givenAStreamOfAuthors_whenProcessedInParallel_countProducesWrongOutput () {assertThat (Executor.countAutors (stream.parallel ())). IsGreaterThan (9); }

Očito je da je nešto pošlo po zlu - dijeljenje streama na slučajnom položaju prouzročilo je dva puta brojanje autora.

4.2. Kako prilagoditi

Da bismo to riješili, moramo provesti a Spliterator koja dijeli autore samo kada su povezani iskaznica i articleId šibice. Evo provedbe našeg običaja Spliterator:

javna klasa RelatedAuthorSpliterator implementira Spliterator {privatni konačni popis popisa; AtomicInteger current = novo AtomicInteger (); // standardni konstruktor / getteri @Override public boolean tryAdvance (Potrošačka akcija) {action.accept (list.get (current.getAndIncrement ())); vratiti current.get () <list.size (); } @Override javni Spliterator trySplit () {int currentSize = list.size () - current.get (); if (currentSize <10) {return null; } za (int splitPos = currentSize / 2 + current.intValue (); splitPos <list.size (); splitPos ++) {if (list.get (splitPos) .getRelatedArticleId () == 0) {Spliterator spliterator = novi RelatedAuthorSpliterator ( list.subList (current.get (), splitPos)); current.set (splitPos); povratni spliterator; }} return null; } @Override public long assessmentSize () {return list.size () - current.get (); } @Preuzmi javne int karakteristike () {return CONCURRENT; }}

Sada se prijavljujem countAuthors () metoda će dati ispravan izlaz. Sljedeći kod pokazuje da:

@Test javna praznina danaAStreamOfAuthors_whenProcessedInParallel_countProducesRightOutput () {Stream stream2 = StreamSupport.stream (spliterator, true); assertThat (Executor.countAutors (stream2.parallel ())). isEqualTo (9); }

Također, običaj Spliterator kreira se iz popisa autora i prelazi kroz njega držeći trenutnu poziciju.

Razmotrimo detaljnije provedbu svake metode:

  • tryAdvance prosljeđuje autore u Potrošač na trenutnom položaju indeksa i povećava njegovu poziciju
  • trySplit definira mehanizam cijepanja, u našem slučaju, RelatedAuthorSpliterator kreira se kada se podudaraju id-ovi, a dijeljenje dijeli popis na dva dijela
  • procijenjenaVeličina - je razlika između veličine popisa i pozicije trenutno ponavljanog autora
  • karakteristike- vraća Spliterator karakteristike, u našem slučaju VELIČINA kao vrijednost koju je vratio procijenjenaVeličina () metoda je točna; štoviše, HITNO ukazuje da je izvor toga Spliterator mogu sigurno mijenjati druge niti

5. Podrška primitivnim vrijednostima

The SpliteratorAPI podržava primitivne vrijednosti uključujući dvostruko, int i dugo.

Jedina razlika između upotrebe generičkog i primitivnog namjenskog Spliterator je dato Potrošač i vrsta Spliterator.

Na primjer, kada nam treba za int vrijednost koju trebamo proći intConsumer. Nadalje, ovdje je popis posvećenih primitivaca Spliteratori:

  • OdPrimitive: nadređeno sučelje za ostale primitive
  • OfInt: A Spliterator specijalizirana za int
  • Dvostruko: A Spliterator posvećen za dvostruko
  • Dugo: A Spliterator posvećen za dugo

6. Zaključak

U ovom smo članku obradili Javu 8 Spliterator uporaba, metode, karakteristike, postupak cijepanja, primitivna podrška i kako je prilagoditi.

Kao i uvijek do kraja, cjelovita provedba ovog članka nalazi se na Githubu.