英文:
Stream mysteriously consumed twice
问题
以下是您提供的代码的翻译部分:
以下代码最终会出现java.lang.IllegalStateException: stream has already been operated upon or closed
错误。
public static void main(String[] args) {
Stream.concat(Stream.of("FOOBAR"),
reverse(StreamSupport.stream(new File("FOO/BAR").toPath().spliterator(), true)
.map(Path::toString)));
}
static <T> Stream<T> reverse(Stream<T> stream) {
return stream.reduce(Stream.empty(),
(Stream<T> a, T b) -> Stream.concat(Stream.of(b), a),
(a, b) -> Stream.concat(b, a));
}
明显的解决方法是使用StreamSupport.stream(…, false)
生成非并行流,但我无法理解为什么不能并行运行。
英文:
The following code ends up with a java.lang.IllegalStateException: stream has already been operated upon or closed
.
public static void main(String[] args) {
Stream.concat(Stream.of("FOOBAR"),
reverse(StreamSupport.stream(new File("FOO/BAR").toPath().spliterator(), true)
.map(Path::toString)));
}
static <T> Stream<T> reverse(Stream<T> stream) {
return stream.reduce(Stream.empty(),
(Stream<T> a, T b) -> Stream.concat(Stream.of(b), a),
(a, b) -> Stream.concat(b, a));
}
The obvious solution is to generate a non parallel stream with StreamSupport.stream(…, false)
, but I can’t see why can’t run in parallel.
答案1
得分: 4
Stream.empty()
不是一个常量。这个方法在每次调用时都会返回一个新的流实例,就像任何其他流一样被消耗,例如当你将它传递给Stream.concat
。
因此,Stream.empty()
不适合作为reduce
的标识值,因为标识值可能会被传递给约定意义模糊的规约函数任意次数的输入。这是一个实现细节,它恰好在串行规约时仅被使用一次,而在并行规约时可能被使用多次。
你可以使用:
static <T> Stream<T> reverse(Stream<T> stream) {
return stream.map(Stream::of)
.reduce((a, b) -> Stream.concat(b, a))
.orElseGet(Stream::empty);
}
代替。
然而,我只将这个解决方案作为学术练习提供。一旦流变得很大,它会导致大量的concat
调用,并且文档中的注释适用:
在构造重复连接的流时要小心。访问深度连接流的元素可能导致深层调用链,甚至可能导致
StackOverflowError
。
一般情况下,当以这种方式使用流API时,生成的底层数据结构会比平面列表昂贵得多。
你可以使用类似这样的代码:
Stream<String> s = Stream.concat(Stream.of("FOOBAR"),
reverse(new File("FOO/BAR").toPath()).map(Path::toString));
static Stream<Path> reverse(Path p) {
ArrayDeque<Path> d = new ArrayDeque<>();
p.forEach(d::addFirst);
return d.stream();
}
或者
static Stream<Path> reverse(Path p) {
Stream.Builder b = Stream.builder();
for(; p != null; p = p.getParent()) b.add(p.getFileName());
return b.build();
}
在Java 9+中,你可以使用一个真正没有额外存储的流(这不一定意味着它会更高效):
static Stream<Path> reverse(Path p) {
return Stream.iterate(p, Objects::nonNull, Path::getParent).map(Path::getFileName);
}
英文:
Stream.empty()
is not a constant. This method returns a new stream instance on each invocation that will get consumed like any other stream, e.g. when you pass it into Stream.concat
.
Therefore, Stream.empty()
is not suitable as identity value for reduce
, as the identity value may get passed as input to the reduction function an arbitrary, intentionally unspecified number of times. It’s an implementation detail that is happens to be used only a single time for sequential reduction and potentially multiple times for parallel reduction.
You can use
static <T> Stream<T> reverse(Stream<T> stream) {
return stream.map(Stream::of)
.reduce((a, b) -> Stream.concat(b, a))
.orElseGet(Stream::empty);
}
instead.
However, I only provide the solution as an academic exercise. As soon as the stream gets large, it leads to an excessive amount of concat
calls and the note of the documentation applies:
> Use caution when constructing streams from repeated concatenation. Accessing an element of a deeply concatenated stream can result in deep call chains, or even StackOverflowError
.
Generally, the resulting underlying data structure will be far more expensive than a flat list, when using the Stream API this way.
You can use something like
Stream<String> s = Stream.concat(Stream.of("FOOBAR"),
reverse(new File("FOO/BAR").toPath()).map(Path::toString));
static Stream<Path> reverse(Path p) {
ArrayDeque<Path> d = new ArrayDeque<>();
p.forEach(d::addFirst);
return d.stream();
}
or
static Stream<Path> reverse(Path p) {
Stream.Builder b = Stream.builder();
for(; p != null; p = p.getParent()) b.add(p.getFileName());
return b.build();
}
With Java 9+ you can use a stream that truly has no additional storage (which does not necessarily imply that it will be more efficient):
static Stream<Path> reverse(Path p) {
return Stream.iterate(p, Objects::nonNull, Path::getParent).map(Path::getFileName);
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论