Spliterators: How a Stream Splits

Streams · lesson 36 of 42 · 4 min read

What parallel() actually does, and why the source decides whether you get a speed-up.

Open this lesson in the learning hub

Key points

  • Every stream sits on a Spliterator: it walks elements one at a time, or hands half away.
  • trySplit() is the whole game. An array or ArrayList splits in O(1) by halving an index range.
  • A LinkedList reports the same flags, but its split walks the chain and copies, so parallel rarely pays.
  • Characteristics such as SIZED, ORDERED and DISTINCT let the pipeline skip work it can prove is pointless.
  • StreamSupport.stream(spliterator, parallel) turns any Iterable or custom source into a real stream.

Example

import java.util.*;
import java.util.stream.*;

public class Main {
    public static void main(String[] args) {
        List<Integer> data = new ArrayList<>(IntStream.rangeClosed(1, 8).boxed().toList());
        Spliterator<Integer> sp = data.spliterator();
        System.out.println("estimateSize : " + sp.estimateSize());

        // trySplit hands half the work away. An ArrayList just halves its index range.
        Spliterator<Integer> firstHalf = sp.trySplit();
        List<Integer> left = new ArrayList<>();
        List<Integer> right = new ArrayList<>();
        firstHalf.forEachRemaining(left::add);
        sp.forEachRemaining(right::add);
        System.out.println("left half    : " + left);
        System.out.println("right half   : " + right);

        // The flags look the same for a LinkedList, but its trySplit has to walk and copy.
        System.out.println("ArrayList    : " + describe(new ArrayList<>(List.of(1, 2, 3, 4)).spliterator()));
        System.out.println("LinkedList   : " + describe(new LinkedList<>(List.of(1, 2, 3, 4)).spliterator()));
        System.out.println("HashSet      : " + describe(new HashSet<>(List.of(1, 2, 3, 4)).spliterator()));

        // Any Iterable becomes a real stream through its spliterator.
        Iterable<String> it = List.of("ada", "grace");
        System.out.println("StreamSupport: "
                + StreamSupport.stream(it.spliterator(), false).map(String::toUpperCase).toList());
    }

    static String describe(Spliterator<?> s) {
        StringJoiner j = new StringJoiner(", ");
        if (s.hasCharacteristics(Spliterator.SIZED)) { j.add("SIZED"); }
        if (s.hasCharacteristics(Spliterator.ORDERED)) { j.add("ORDERED"); }
        if (s.hasCharacteristics(Spliterator.DISTINCT)) { j.add("DISTINCT"); }
        if (s.hasCharacteristics(Spliterator.SUBSIZED)) { j.add("SUBSIZED"); }
        return j.toString();
    }
}

Parallel speed comes from a cheap trySplit, which comes from the source you chose.

This is a reading copy. The full lesson — with the visual explainer, the interactive lab and a Run button for the code — lives in the Streams course, and every lesson in it is listed on the Streams contents page.