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
,僅在相關的id
和articleId
匹配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
支持原始值,包括double
, int
和long
。
使用通用和原始專用Spliterator
之間的唯一區別是給定的Consumer
和Spliterator
的類型。
例如,當我們需要一個int
值時,我們需要傳遞一個intConsumer
。此外,這是原始的專用Spliterators
的列表:
-
OfPrimitive<T, T_CONS, T_SPLITR extends Spliterator.OfPrimitive<T, T_CONS, T_SPLITR>>
:其他原語的父接口 -
OfInt
:專用於int
的Spliterator
-
OfDouble
:一個Spliterator
專用於double
-
OfLong
:一個Spliterator
專用於long
六,結論
在本文中,我們介紹了Java 8 Spliterator
用法,方法,特性,拆分過程,原始支持以及如何對其進行自定義。
與往常一樣,可以在Github上找到本文的完整實現。