Java Spliterator簡介

1.概述

Java 8中引入的Spliterator接口可用於遍歷和分區序列。它是Streams的基本實用程序,尤其是並行的。

在本文中,我們將介紹其用法,特徵,方法以及如何創建自己的自定義實現。

2. Spliterator API

2.1。 tryAdvance

這是用於逐步執行序列的主要方法。該方法將使用一個Consumer ,該Consumer用於按順序Spliterator使用Spliterator元素,如果沒有要遍歷的元素,則返回false

在這裡,我們將看一下如何使用它遍歷和分割元素。

首先,假設我們有一個包含35000條文章的ArrayList ,並將Article類定義為:

public class Article {

 private List<Author> listOfAuthors;

 private int id;

 private String name;



 // standard constructors/getters/setters

 }

現在,讓我們實現一個處理文章列表的任務,並為每個文章名稱添加一個後綴“ – published by Baeldung”

public String call() {

 int current = 0;

 while (spliterator.tryAdvance(a -> a.setName(article.getName()

 .concat("- published by Baeldung")))) {

 current++;

 }



 return Thread.currentThread().getName() + ":" + current;

 }

請注意,此任務完成執行後將輸出已處理項目的數量。

另一個要點是,我們使用了tryAdvance()方法來處理下一個元素。

2.2。 trySplit

接下來,讓我們拆分Spliterators (因此得名)並獨立處理分區。

trySplit方法嘗試將其分為兩部分。然後,調用者進程元素,最後,返回的實例將處理其他實例,從而允許並行處理兩者。

讓我們首先生成列表:

public static List<Article> generateElements() {

 return Stream.generate(() -> new Article("Java"))

 .limit(35000)

 .collect(Collectors.toList());

 }

接下來,我們使用spliterator()方法獲取我們的Spliterator實例。然後我們應用trySplit()方法:

@Test

 public void givenSpliterator_whenAppliedToAListOfArticle_thenSplittedInHalf() {

 Spliterator<Article> split1 = Executor.generateElements().spliterator();

 Spliterator<Article> split2 = split1.trySplit();



 assertThat(new Task(split1).call())

 .containsSequence(Executor.generateElements().size() / 2 + "");

 assertThat(new Task(split2).call())

 .containsSequence(Executor.generateElements().size() / 2 + "");

 }

拆分過程按預期進行,並且將記錄平均分配

2.3。 estimatedSize

estimatedSize方法為我們提供了估計的元素數量:

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

這將輸出:

Size: 17500

2.4。 hasCharacteristics

該API檢查給定的特徵是否與Spliterator.的屬性匹配Spliterator.然後,如果我們調用上述方法,則輸出將是這些特徵的int表示:

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

3. Spliterator特性

它具有八個描述其行為的不同特徵。這些可以用作外部工具的提示:

  • SIZED 如果它是能夠與所述返回元件的確切數量的estimateSize()方法
  • SORTED -如果它是通過迭代的排序源
  • SUBSIZED –如果我們使用trySplit()方法拆分實例並獲得同樣為SIZED Spliterators
  • CONCURRENT -如果源可以安全地修改並發
  • DISTINCT –如果對於每對遇到的元素x, y, !x.equals(y)
  • IMMUTABLE –如果無法對源持有的元素進行結構修改
  • NONNULL –源是否為空
  • ORDERED –如果在有序序列上進行迭代

4.自定義Spliterator

4.1。何時定制

首先,讓我們假設以下情形:

我們有一個帶有作者列表的文章類,並且該文章可以有多個作者。此外,如果與他相關的文章ID與文章ID相匹配,我們將認為與該文章有關的作者。

我們的Author類如下所示:

public class Author {

 private String name;

 private int relatedArticleId;



 // standard getters, setters & constructors

 }

接下來,我們將實現一個類來遍歷作者流時對作者進行計數。然後,該類將對流進行歸約

讓我們看一下類的實現:

public class RelatedAuthorCounter {

 private int counter;

 private boolean isRelated;



 // standard constructors/getters



 public RelatedAuthorCounter accumulate(Author author) {

 if (author.getRelatedArticleId() == 0) {

 return isRelated ? this : new RelatedAuthorCounter( counter, true);

 } else {

 return isRelated ? new RelatedAuthorCounter(counter + 1, false) : this;

 }

 }



 public RelatedAuthorCounter combine(RelatedAuthorCounter RelatedAuthorCounter) {

 return new RelatedAuthorCounter(

 counter + RelatedAuthorCounter.counter,

 RelatedAuthorCounter.isRelated);

 }

 }

上一類中的每個方法都執行特定的操作以在遍歷時進行計數。

首先, accumulate()方法以迭代方式一次遍歷作者,然後**combine()使用它們的值對兩個計數器求和**。最後, getCounter()返回計數器。

現在,測試我們到目前為止所做的。讓我們將本文的作者列表轉換為作者流:

Stream<Author> stream = article.getListOfAuthors().stream();

並實現一個**countAuthor()方法以使用RelatedAuthorCounter對流進行RelatedAuthorCounter** :

private int countAutors(Stream<Author> stream) {

 RelatedAuthorCounter wordCounter = stream.reduce(

 new RelatedAuthorCounter(0, true),

 RelatedAuthorCounter::accumulate,

 RelatedAuthorCounter::combine);

 return wordCounter.getCounter();

 }

如果我們使用順序流,則輸出將達到預期的“count = 9” ,但是,當我們嘗試並行化操作時會出現問題。

讓我們看一下以下測試用例:

@Test

 void

 givenAStreamOfAuthors_whenProcessedInParallel_countProducesWrongOutput() {

 assertThat(Executor.countAutors(stream.parallel())).isGreaterThan(9);

 }

顯然,出了點問題–在隨機位置拆分流會導致作者被計數兩次。

4.2。如何定制

為了解決這個問題,我們需要實現一個Spliterator ,僅在相關的idarticleId匹配Spliterator對作者進行拆分。這是我們自定義的Spliterator的實現:

public class RelatedAuthorSpliterator implements Spliterator<Author> {

 private final List<Author> list;

 AtomicInteger current = new AtomicInteger();

 // standard constructor/getters



 @Override

 public boolean tryAdvance(Consumer<? super Author> action) {

 action.accept(list.get(current.getAndIncrement()));

 return current.get() < list.size();

 }



 @Override

 public Spliterator<Author> 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<Author> spliterator

 = new RelatedAuthorSpliterator(

 list.subList(current.get(), splitPos));

 current.set(splitPos);

 return spliterator;

 }

 }

 return null;

 }



 @Override

 public long estimateSize() {

 return list.size() - current.get();

 }



 @Override

 public int characteristics() {

 return CONCURRENT;

 }

 }

現在應用countAuthors()方法將提供正確的輸出。下面的代碼演示了這一點:

@Test

 public void

 givenAStreamOfAuthors_whenProcessedInParallel_countProducesRightOutput() {

 Stream<Author> stream2 = StreamSupport.stream(spliterator, true);



 assertThat(Executor.countAutors(stream2.parallel())).isEqualTo(9);

 }

此外,自定義Spliterator是從作者列表中創建的,並通過保持當前位置遍歷自定義Spliterator

讓我們更詳細地討論每種方法的實現:

  • **tryAdvance** –在當前索引位置將作者傳遞給Consumer ,並增加其位置
  • **trySplit** –定義拆分機制,在我們的示例中,當id匹配時創建RelatedAuthorSpliterator ,拆分將列表分為兩部分
  • estimatedSize –是列表大小和當前迭代作者的位置之間的差
  • **characteristics** –返回Spliterator符特性,在我們的情況下為SIZED因為estimatedSize()方法返回的estimatedSize()是精確的;此外, CONCURRENT表示此Spliterator的源可以被其他線程安全地修改。

5.支持原始值

Spliterator API支持原始值,包括doubleintlong

使用通用和原始專用Spliterator之間的唯一區別是給定的ConsumerSpliterator的類型。

例如,當我們需要一個int值時,我們需要傳遞一個intConsumer 。此外,這是原始的專用Spliterators的列表:

  • OfPrimitive<T, T_CONS, T_SPLITR extends Spliterator.OfPrimitive<T, T_CONS, T_SPLITR>> :其他原語的父接口
  • OfInt :專用於intSpliterator
  • OfDouble :一個Spliterator專用於double
  • OfLong :一個Spliterator專用於long

六,結論

在本文中,我們介紹了Java 8 Spliterator用法,方法,特性,拆分過程,原始支持以及如何對其進行自定義。

與往常一樣,可以在Github上找到本文的完整實現。