不可变链表的拆分器
Spliterator for immutable linked list
这是不可变链表的经典实现:
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; }
}
spliterator()
的默认实现不支持高效并行化:
List<Integer> list = List.<Integer> nil().cons(3).cons(2).cons(1);
list.parallelStream().forEach(i -> {
System.out.println(i);
try {
Thread.sleep(1000);
} catch (Exception e) {
e.printStackTrace();
}
});
这将按顺序打印 1, 2, 3
。
如何实现spliterator()
以支持高效并行化?
您可能会使用一些带有 交错 的算法 - 例如,对元素进行计数并在整数除法后使用余数。这可以拆分元素以进行并行迭代。
您也可以在构造迭代器之前迭代一次,将列表拆分为 间隔 ,但这会破坏流的目的 - 例如如果你用它 anyMatch
,它会减慢你的速度。
没有真正有效 拆分链表的方法(在不到线性时间内),除非您创建自己的链表实现并包含一些附加信息。
编辑: 哦等等 - 你只实现了 Iterable
。这是非常有限的,你必须想出一种只有一次通过的算法。这意味着,拆分本身根本不会并行,因此您不妨在流程的其他地方强制执行并行性。
甚至无法报告估计大小(这是 Iterable
的默认实现)的拆分器被并行管道拆分得很差。如果跟踪 List
的大小,则可以解决此问题。在您的情况下,跟踪确切大小并不难:
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;
}
}
之后并行化会更好。请注意,它仍然是 poor man parallelization,因为你不能快速跳到列表的中间,但在很多情况下它会提供合理的加速。
另请注意,最好明确指定 Spliterator.ORDERED
特征。否则,在并行流操作中可能会忽略该顺序,即使它是明确请求的(例如,通过 forEachOrdered()
终端操作)。
这是不可变链表的经典实现:
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; }
}
spliterator()
的默认实现不支持高效并行化:
List<Integer> list = List.<Integer> nil().cons(3).cons(2).cons(1);
list.parallelStream().forEach(i -> {
System.out.println(i);
try {
Thread.sleep(1000);
} catch (Exception e) {
e.printStackTrace();
}
});
这将按顺序打印 1, 2, 3
。
如何实现spliterator()
以支持高效并行化?
您可能会使用一些带有 交错 的算法 - 例如,对元素进行计数并在整数除法后使用余数。这可以拆分元素以进行并行迭代。
您也可以在构造迭代器之前迭代一次,将列表拆分为 间隔 ,但这会破坏流的目的 - 例如如果你用它 anyMatch
,它会减慢你的速度。
没有真正有效 拆分链表的方法(在不到线性时间内),除非您创建自己的链表实现并包含一些附加信息。
编辑: 哦等等 - 你只实现了 Iterable
。这是非常有限的,你必须想出一种只有一次通过的算法。这意味着,拆分本身根本不会并行,因此您不妨在流程的其他地方强制执行并行性。
甚至无法报告估计大小(这是 Iterable
的默认实现)的拆分器被并行管道拆分得很差。如果跟踪 List
的大小,则可以解决此问题。在您的情况下,跟踪确切大小并不难:
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;
}
}
之后并行化会更好。请注意,它仍然是 poor man parallelization,因为你不能快速跳到列表的中间,但在很多情况下它会提供合理的加速。
另请注意,最好明确指定 Spliterator.ORDERED
特征。否则,在并行流操作中可能会忽略该顺序,即使它是明确请求的(例如,通过 forEachOrdered()
终端操作)。