1. 概述

Spliterator 是 Java 8 引入的核心接口,用于遍历和分割序列。它是 Stream(特别是并行流)的基础工具。

本文将深入探讨其用法、特性、核心方法,以及如何实现自定义 Spliterator。

2. Spliterator API

2.1. tryAdvance

这是遍历序列的核心方法。它接收一个 Consumer按顺序逐个消费 Spliterator 中的元素,当没有元素可遍历时返回 false

下面通过示例展示如何遍历和分割元素:

假设有一个包含 35000 篇文章的 ArrayListArticle 类定义如下:

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 支持 intlongdouble 三种原生类型。

与泛型 Spliterator 的区别在于:

  • 需使用对应的 Consumer(如 IntConsumer
  • 使用专用 Spliterator 接口:
接口 说明
OfPrimitive 原生类型父接口
OfInt 专用 int 类型
OfDouble 专用 double 类型
OfLong 专用 long 类型

6. 总结

本文深入探讨了 Java 8 Spliterator 的:

  • 核心方法(tryAdvance/trySplit
  • 特性标识
  • 分割机制
  • 原生类型支持
  • 自定义实现

完整代码实现可在 GitHub 查看。


原始标题:Introduction to Spliterator in Java | Baeldung