Java 不可变链表的拆分器

Java 不可变链表的拆分器,java,linked-list,java-8,singly-linked-list,spliterator,Java,Linked List,Java 8,Singly Linked List,Spliterator,这是不可变链表的经典实现: public abstract class List<A> implements Iterable<A> { private static final List NIL = new Nil(); public abstract A head(); public abstract List<A> tail(); public List<A> cons(A a) { return new C

这是不可变链表的经典实现:

public abstract class List<A> implements Iterable<A> {
    private static final List NIL = new Nil();

    public abstract A head();
    public abstract List<A> tail();
    public List<A> cons(A a) { return new Cons<>(a, this); }

    public static <A> List<A> nil() { return NIL; }

    @Override
    public Iterator<A> iterator() {
        return new Iterator<A>() {
            private List<A> list = List.this;

            @Override
            public boolean hasNext() {
                return list != NIL;
            }

            @Override
            public A next() {
                A n = list.head();
                list = list.tail();
                return n;
            }
        };
    }

    public Stream<A> stream() {
        return StreamSupport.stream(spliterator(), false);
    }

    public Stream<A> parallelStream() {
        return StreamSupport.stream(spliterator(), true);
    }
}

class Nil extends List {
    @Override public Object head() { throw new NoSuchElementException(); }
    @Override public List tail() { throw new NoSuchElementException(); }
}

class Cons<A> extends List<A> {
    private final A head;
    private final List<A> tail;

    Cons(A head, List<A> tail) {
        this.head = head;
        this.tail = tail;
    }

    @Override public A head() { return head; }
    @Override public List<A> tail() { return tail; }
}
这将按顺序打印
1、2、3


如何实现
spliterator()
以支持有效的并行化?

您可以使用一些交错算法,例如,计算元素数并在整数除法后使用余数。这可以分割元素进行并行迭代

您也可以在构建迭代器之前进行一次迭代,将列表拆分为多个间隔,但这将超出流的目的-例如,如果您将其用于
anyMatch
,它将大大降低您的速度

没有真正有效的方法来拆分链表(在少于线性时间内),除非您创建自己的链表实现,其中包含一些附加信息


编辑:哦,等等-您只能实现
Iterable
。这是相当有限的,你必须想出一个只有一次通过的算法。这意味着,拆分本身根本不会是并行的,因此您不妨在流程中的其他地方强制执行您的并行性。

即使无法报告估计大小(这是Iterable的默认实现)的拆分器也无法通过并行管道进行很好的拆分。如果跟踪
列表的大小,可以解决此问题。在您的情况下,跟踪准确的尺寸并不困难:

public abstract class List<A> implements Iterable<A> {
    ...
    public abstract long size();

    @Override
    public Spliterator<A> spliterator() {
        return Spliterators.spliterator(iterator(), size(), Spliterator.ORDERED);
    }
}

class Nil extends List {
    ...
    public long size() {
        return 0;
    }
}

class Cons<A> extends List<A> {
    ...
    private final long size;

    Cons(A head, List<A> tail) {
        this.head = head;
        this.tail = tail;
        this.size = tail.size()+1;
    }

    ...

    @Override
    public long size() {
        return size;
    }
}
公共抽象类列表实现Iterable{
...
公共抽象长尺寸();
@凌驾
公共拆分器拆分器(){
返回Spliterators.spliterator(迭代器(),size(),spliterator.ORDERED);
}
}
类Nil扩展列表{
...
公共长码(){
返回0;
}
}
类Cons扩展列表{
...
私人最终长尺寸;
缺点(头、尾){
这个头=头;
this.tail=tail;
this.size=tail.size()+1;
}
...
@凌驾
公共长码(){
返回大小;
}
}
在那之后,并行化将工作得更好。请注意,它仍然是穷人的并行化,因为您无法快速跳转到列表的中间,但在许多情况下,它将提供合理的加速


还要注意,最好明确指定
拆分器.ORDERED
特性。否则,在并行流操作中,即使是显式请求的顺序(例如,通过
forEachOrdered()
terminal操作),也可能会忽略顺序。

考虑一下这篇文章:与可以实现SkipList数据结构的列表不同,这些数据结构更易于并行化。您计划对每个元素做多少工作?只有当它的重量大于100µs时,才有必要将其拆分为单个元素。您要问的是如何使用数据结构,这通常对大量元素都是有害的,同时还有一个只与大量元素相关的功能。不要惊讶这并没有真正得到回报。链接列表在教室里很好,但我在实践中从未见过任何实际的使用案例。@Holger我不确定平行性与集合中元素的数量有什么关系。如果我想并行处理3项繁重的任务,该怎么办?
public abstract class List<A> implements Iterable<A> {
    ...
    public abstract long size();

    @Override
    public Spliterator<A> spliterator() {
        return Spliterators.spliterator(iterator(), size(), Spliterator.ORDERED);
    }
}

class Nil extends List {
    ...
    public long size() {
        return 0;
    }
}

class Cons<A> extends List<A> {
    ...
    private final long size;

    Cons(A head, List<A> tail) {
        this.head = head;
        this.tail = tail;
        this.size = tail.size()+1;
    }

    ...

    @Override
    public long size() {
        return size;
    }
}