考虑这个小程序,我们获取一个流,对其进行排序,映射,然后迭代:
public class AlphabetOrdinals {
private static final List<Character> ALPHABET = List.of('a', 'b', 'c', 'd', 'e', 'f');
private static final int STOP_ORDINAL = 'b' - 'a';
public static void main(String[] args) {
System.out.println("java.runtime.version = " + System.getProperty("java.runtime.version"));
Stream<Integer> ordinals = ALPHABET.stream()
.sorted()
.map(AlphabetOrdinals::ordinal);
int count = 0;
Iterator<Integer> iterator = ordinals.iterator();
while (iterator.hasNext()) {
int ordinal = iterator.next();
if (ordinal > STOP_ORDINAL) {
System.out.println("stopping at " + ordinal);
break;
}
System.out.println("consuming " + ordinal);
++count;
}
System.out.println("consumed " + count + " ordinals");
}
private static int ordinal(char letter) {
int ordinal = letter - 'a';
System.out.println("performing EXTREMELY EXPENSIVE mapping of " + letter + " -> " + ordinal);
return ordinal;
}
}这个程序很愚蠢,但它是从一个真实的程序简化而来的,在这个程序中,迭代与另一个流上的迭代交织在一起,所以我不能很容易地将它替换为takeWhile/forEach。
我希望这个程序打印:
java.runtime.version = 11+28
performing EXTREMELY EXPENSIVE mapping of a -> 0
consuming 0
performing EXTREMELY EXPENSIVE mapping of b -> 1
consuming 1
performing EXTREMELY EXPENSIVE mapping of c -> 2
stopping at 2
consumed 2 ordinals但它会打印:
java.runtime.version = 11+28
performing EXTREMELY EXPENSIVE mapping of a -> 0
performing EXTREMELY EXPENSIVE mapping of b -> 1
performing EXTREMELY EXPENSIVE mapping of c -> 2
performing EXTREMELY EXPENSIVE mapping of d -> 3
performing EXTREMELY EXPENSIVE mapping of e -> 4
performing EXTREMELY EXPENSIVE mapping of f -> 5
consuming 0
consuming 1
stopping at 2
consumed 2 ordinals如果我删除.sorted(),它会打印出我所期望的内容。
这一切为什么要发生?
在实际的程序中,映射步骤涉及从速度较慢的网络驱动器读取大量数据,因此我不希望在绝对必要的情况下多次执行此操作!
发布于 2019-11-21 02:32:04
无聊的答案:
这就是streams API实现的编写方式。
不那么无聊的答案:
流具有某种类型的操作链,以应用于输入。对于引用流,添加的排序操作是:java.util.stream.SortedOps.RefSortingSink (假设您有一个与我类似的JDK )。对于map,它是:
new StatelessOp<P_OUT, R>(this, StreamShape.REFERENCE,
StreamOpFlag.NOT_SORTED | StreamOpFlag.NOT_DISTINCT) {
@Override
Sink<P_OUT> opWrapSink(int flags, Sink<R> sink) {
return new Sink.ChainedReference<P_OUT, R>(sink) {
@Override
public void accept(P_OUT u) {
downstream.accept(mapper.apply(u));
}
};
}
};java.util.stream.SortedOps.RefSortingSink实现的相关部分如下:
@Override
public void begin(long size) {
if (size >= Nodes.MAX_ARRAY_SIZE)
throw new IllegalArgumentException(Nodes.BAD_SIZE);
list = (size >= 0) ? new ArrayList<T>((int) size) : new ArrayList<T>();
}
@Override
public void end() {
list.sort(comparator);
downstream.begin(list.size());
if (!cancellationWasRequested) {
list.forEach(downstream::accept);
}
else {
for (T t : list) {
if (downstream.cancellationRequested()) break;
downstream.accept(t);
}
}
downstream.end();
list = null;
}
@Override
public void accept(T t) {
list.add(t);
}正如您所看到的,排序将整个列表传递给链中的下一个操作(下一个操作称为downstream)。然而,map操作接收它接收到的任何内容,使用映射函数并将其传递给下游。这意味着如果你只使用map,你会得到懒惰的预期行为,而如果你使用排序,整个现在排序的流在list.forEach(downstream::accept)中被塞进map的喉管,map不能拒绝接受它,或者只接受它的一部分。
https://stackoverflow.com/questions/58960735
复制相似问题