Skip to Content
Course content

264: The Spliterator Interface

Click on the "Edit" button in the top corner of the screen to edit your slide content.

Wait, I already know Iterators. Why do I need a "Spliterator"?

If you've spent any time with the Collections API, you know Iterator is the gold standard for walking through a list. But Iterator is strictly sequential. It's a one-way street. When Java 8 introduced Parallel Streams, the engineers realized they needed a way to not just move forward, but to actually carve a data source into chunks that could be processed by different CPU cores simultaneously.

That's where the Spliterator (short for "splitable iterator") comes in. While an Iterator asks "What is the next element?", a Spliterator asks "Can I split myself in half so another thread can take over part of the work?" If you're just writing a simple for loop, you'll never touch this. But if you're building a custom data structure and you want it to play nicely with .parallelStream(), you have to implement this interface.

How do tryAdvance and trySplit actually work in practice?

The API feels a bit weird at first because it doesn't use the hasNext()/next() pattern. Instead, it uses tryAdvance(). I like to think of tryAdvance() as "try to do one thing with the next element." It takes a Consumer, performs the action, and returns true if an element existed, or false if you've hit the end.

The real magic is trySplit(). When the Stream framework decides to go parallel, it calls trySplit(). This method should partition off a portion of the remaining elements into a new Spliterator. The original Spliterator keeps the rest. This happens recursively until the chunks are small enough to be processed efficiently.

// A simplified glimpse at the logic flow
Spliterator split = mainSpliterator.trySplit(); 
if (split != null) {
    // Now we have two Spliterators that can be processed by two different threads
}

Can you show me how to implement one for a custom data source?

Let's say we have a NumberRange class that represents a huge span of integers. We don't want to actually create a List of a billion integers because that would kill our heap memory. Instead, we'll create a custom Spliterator that calculates the numbers on the fly.

import java.util.Spliterator;
import java.util.function.Consumer;

public class RangeSpliterator implements Spliterator {
    private int current;
    private final int end;

    public RangeSpliterator(int start, int end) {
        this.current = start;
        this.end = end;
    }

    @Override
    public boolean tryAdvance(Consumer<Integer> action) {
        if (current < end) {
            action.accept(current++);
            return true;
        }
        return false;
    }

    @Override
    public Spliterator<Integer> trySplit() {
        int remaining = end - current;
        if (remaining < 10) { // Don't split if the chunk is too small
            return null;
        }
        int mid = current + (remaining / 2);
        // Split off the first half into a new Spliterator
        Spliterator<Integer> split = new RangeSpliterator(current, mid);
        // The current Spliterator now handles the second half
        this.current = mid;
        return split;
    }

    @Override
    public long estimateSize() {
        return end - current;
    }

    @Override
    public int characteristics() {
        // IMMUTABLE and ORDERED are helpful hints for the Stream API to optimize
        return Spliterator.ORDERED | Spliterator.IMMUTABLE;
    }
}

In this example, trySplit() effectively divides the range in half. If you wrap this in a StreamSupport.stream() call and run it with .parallel(), Java will use these splits to distribute the number-crunching across all your available cores.




📋 Practical Task

Building a Parallel Segmented-Log Spliterator

You are working on a system that processes massive log files. To avoid loading the entire file into memory, you've decided to implement a custom Spliterator that handles "virtual" segments of a log.

Your Task: Create a class named LogSegmentSpliterator that implements Spliterator<String>. This Spliterator should simulate a segment of a log file by taking a startLine and an endLine in its constructor.

  • tryAdvance(Consumer<String> action): Should return a string like "Log line [number]" for the current line, increment the counter, and return true. Return false when currentLine >= endLine.
  • trySplit(): If there are more than 100 lines remaining, split the current range in half and return a new LogSegmentSpliterator for the first half. Update the current Spliterator to cover the second half. Otherwise, return null.
  • estimateSize(): Return the number of lines remaining in the current segment.
  • characteristics(): Return Spliterator.ORDERED.

Test your implementation by using StreamSupport.stream() to create a stream from your Spliterator, calling .parallel(), and printing the results to verify that the segments are being processed.

Rating
0 0

There are no comments for now.

to be the first to leave a comment.