1. 概述
Spliterator
是 Java 8 引入的核心接口,用于遍历和分割序列。它是 Stream(特别是并行流)的基础工具。
本文将深入探讨其用法、特性、核心方法,以及如何实现自定义 Spliterator。
2. Spliterator API
2.1. tryAdvance
这是遍历序列的核心方法。它接收一个 Consumer
,按顺序逐个消费 Spliterator 中的元素,当没有元素可遍历时返回 false
。
下面通过示例展示如何遍历和分割元素:
假设有一个包含 35000 篇文章的 ArrayList
,Article
类定义如下:
public class Article {
private List<Author> listOfAuthors;
private int id;
private String name;
// 标准构造器/getter/setter
}
现在用 Spliterator 处理文章列表,为每篇文章名称添加后缀 "- published by Baeldung"
:
@Test
public void givenAStreamOfArticles_whenProcessedInSequentiallyWithSpliterator_ProducessRightOutput() {
// ...
}
首先生成文章数据:
public void givenAStreamOfArticles_whenProcessedInSequentiallyWithSpliterator_ProducessRightOutput() {
List<Article> articles = Stream.generate(() -> new Article("Java"))
.limit(35000)
.collect(Collectors.toList());
// ...
}
使用 Stream 生成了 35000 篇文章。接下来创建 Spliterator 并用 tryAdvance
处理:
Spliterator<Article> spliterator = articles.spliterator();
while (spliterator.tryAdvance(article -> article.setName(article.getName()
.concat("- published by Baeldung"))));
这里的 Consumer 是一个简单的函数,用于修改文章名称。
最后验证所有文章是否被处理:
articles.forEach(article -> assertThat(article.getName()).isEqualTo("Java- published by Baeldung"));
✅ 测试通过:所有文章名称已更新为 Java- published by Baeldung
。
关键点:我们使用 tryAdvance()
逐个处理元素。
2.2. trySplit
接下来分割 Spliterator(顾名思义),独立处理分区。
trySplit
尝试将 Spliterator 分成两部分:调用者处理一部分,返回的实例处理另一部分,实现并行处理。
生成文章和 Spliterator:
@Test
public void givenAStreamOfArticle_whenProcessedUsingTrySplit_thenSplitIntoEqualHalf() {
List<Article> articles = Stream.generate(() -> new Article("Java"))
.limit(35000)
.collect(Collectors.toList());
Spliterator<Article> split1 = articles.spliterator();
// ...
}
通过 trySplit
创建第二个 Spliterator:
Spliterator<Article> split2 = split1.trySplit();
创建两个列表存储处理结果:
List<Article> articlesListOne = new ArrayList<>();
List<Article> articlesListTwo = new ArrayList<>();
消费元素:
split1.forEachRemaining(articlesListOne::add);
split2.forEachRemaining(articlesListTwo::add);
验证分区是否均分(各 17500 篇):
assertThat(articlesListOne.size()).isEqualTo(17500);
assertThat(articlesListTwo.size()).isEqualTo(17500);
验证元素是否互斥:
assertThat(articlesListOne).doesNotContainAnyElementsOf(articlesListTwo);
✅ 测试通过:两个列表元素完全独立,证明分区可独立处理。
分割过程按预期均分了数据。
2.3. estimatedSize
estimatedSize
返回元素数量的估计值:
log.info("Size: " + split1.estimateSize());
输出:
Size: 17500
2.4. hasCharacteristics
检查 Spliterator 是否具有指定特性,返回特性的二进制表示:
log.info("Characteristics: " + split1.characteristics());
输出:
Characteristics: 16464
3. Spliterator 特性
Spliterator 有 8 种特性描述其行为,可作为外部工具的提示:
特性 | 说明 |
---|---|
SIZED | 能通过 estimateSize() 返回精确元素数量 |
SORTED | 源数据已排序 |
SUBSIZED | 分割后的子 Spliterator 也是 SIZED |
CONCURRENT | 源数据可安全并发修改 |
DISTINCT | 所有元素互不相同(x.equals(y) 为 false) |
IMMUTABLE | 源数据不可变 |
NONNULL | 源数据不含 null |
ORDERED | 遍历顺序固定 |
4. 自定义 Spliterator
4.1. 何时自定义
自定义 Spliterator 适用于需要精细控制遍历逻辑的场景,例如:
- 处理自定义对象数组
- 读取 IO 通道数据
- 遍历生成器函数
4.2. 如何自定义
假设要计算大型 Integer 数组的和,需实现一个分割整数列表的 Spliterator:
public class CustomSpliterator implements Spliterator<Integer> {
private final List<Integer> elements;
private int currentIndex;
public CustomSpliterator(List<Integer> elements) {
this.elements = elements;
this.currentIndex = 0;
}
@Override
public boolean tryAdvance(Consumer<? super Integer> action) {
if (currentIndex < elements.size()) {
action.accept(elements.get(currentIndex));
currentIndex++;
return true;
}
return false;
}
@Override
public Spliterator<Integer> trySplit() {
int currentSize = elements.size() - currentIndex;
if (currentSize < 2) {
return null;
}
int splitIndex = currentIndex + currentSize / 2;
CustomSpliterator newSpliterator = new CustomSpliterator(elements.subList(currentIndex, splitIndex));
currentIndex = splitIndex;
return newSpliterator;
}
@Override
public long estimateSize() {
return elements.size() - currentIndex;
}
@Override
public int characteristics() {
return ORDERED | SIZED | SUBSIZED | NONNULL;
}
}
顺序处理测试:
@Test
public void givenAStreamOfIntegers_whenProcessedSequentialCustomSpliterator_countProducesRightOutput() {
List<Integer> numbers = new ArrayList<>();
numbers.add(1);
numbers.add(2);
numbers.add(3);
numbers.add(4);
numbers.add(5);
CustomSpliterator customSpliterator = new CustomSpliterator(numbers);
AtomicInteger sum = new AtomicInteger();
customSpliterator.forEachRemaining(sum::addAndGet);
assertThat(sum.get()).isEqualTo(15);
}
并行处理测试:
@Test
public void givenAStreamOfIntegers_whenProcessedInParallelWithCustomSpliterator_countProducesRightOutput() {
List<Integer> numbers = new ArrayList<>();
numbers.add(1);
numbers.add(2);
numbers.add(3);
numbers.add(4);
numbers.add(5);
CustomSpliterator customSpliterator = new CustomSpliterator(numbers);
// 创建 ForkJoinPool 用于并行处理
ForkJoinPool forkJoinPool = ForkJoinPool.commonPool();
AtomicInteger sum = new AtomicInteger(0);
// 使用 parallelStream 并行处理元素
forkJoinPool.submit(() -> customSpliterator.forEachRemaining(sum::addAndGet)).join();
assertThat(sum.get()).isEqualTo(15);
}
⚠️ 并行处理通过分割数据提升性能,适合大数据集或计算密集型任务。
实现细节解析:
- 构造器:接收整数列表并初始化当前索引
- **tryAdvance()**:消费下一个元素,存在则返回
true
并推进索引 - **trySplit()**:将剩余元素均分,创建新 Spliterator 处理前半部分
- **estimateSize()**:返回剩余元素数量
- **characteristics()**:声明特性(有序/大小已知/子大小已知/非空)
5. 原生类型支持
Spliterator API 支持 int
、long
、double
三种原生类型。
与泛型 Spliterator 的区别在于:
- 需使用对应的
Consumer
(如IntConsumer
) - 使用专用 Spliterator 接口:
接口 | 说明 |
---|---|
OfPrimitive | 原生类型父接口 |
OfInt | 专用 int 类型 |
OfDouble | 专用 double 类型 |
OfLong | 专用 long 类型 |
6. 总结
本文深入探讨了 Java 8 Spliterator 的:
- 核心方法(
tryAdvance
/trySplit
) - 特性标识
- 分割机制
- 原生类型支持
- 自定义实现
完整代码实现可在 GitHub 查看。