在不关闭流的情况下处理流中的一个元素

8

我希望找到一种简洁高效的方法,将一个消费者应用于非并行流的一个元素,而不关闭该流。

我的意思是,我想替换

AtomicBoolean firstOneDone = new AtomicBoolean();
lines.forEach(line -> {
    if (!firstOneDone.get()) {
        // handle first line
        firstOneDone.set(true);
    } else {
        // handle any other line
    }
})

与某物相似

lines.forFirst(header -> {
    // handle first line        
}).forEach(line -> {
    // handle any other line
})

我不想对整个流进行两次操作(复制或重新创建流,peek等),也不想将布尔值/测试移动到另一个位置,比如函数包装器。

这种部分读取的方式是否可行,还是说流的基本模型与此不兼容?


@assylias 这意味着“在整个流上进行两次遍历”。 - Florian Margaine
2
@FlorianMargaine 流通常是惰性的,因此第一行只会“读取”流的第一个元素,并且不会处理流的其余部分。 - assylias
@FlorianMargaine 实际上,使用非并行流不会进行两次遍历。这看起来是一个有效且有趣的答案。 - Denys Séguret
@FlorianMargaine 如果它被设计成这样,那将非常低效,因为filterfindFirst会导致检查Stream的所有元素,而这并不是必要的(甚至对于无限流也不起作用);-) - Alexis C.
1
获取 Java 8 Stream 中下一个元素的方法: - Alex - GlassEditor.com
显示剩余5条评论
3个回答

10

不可能,因为每次执行“终止”操作时,您的流管道都会关闭。另一种选择是使用Stream的迭代器。您将只有一个迭代器。我猜这正是您实际想要的,因为您坚持只创建一个流。但是,您将不得不跳过“函数式”部分。

Stream<String> strings = ... ;
Iterator<String> stringsIt = strings.iterator();
if (stringsIt.hasNext()) {
  System.out.printf("header: %s%n", stringsIt.next());
  while (stringsIt.hasNext()) {
    System.out.printf("line: %s%n", stringsIt.next());
  }
}

ZouZou的评论后,另一个选择:

Stream<String> strings = ... ;
Iterator<String> stringsIt = strings.iterator();
if (stringsIt.hasNext()) {
  System.out.printf("header: %s%n", stringsIt.next());
  stringsIt.forEachRemaining(line -> { System.out.printf("line: %s%n", line); });
}
一个包含所有功能的最终答案实际上是以下内容:
Stream<String> lines = ... ;
Spliterator<String> linesIt = lines.spliterator();
linesIt.tryAdvance(header -> { System.out.printf("header: %s%n", header); });
linesIt.forEachRemaining(line -> { System.out.printf("line: %s%n", line); });

4
流对象确实有一个iterator()方法。而且while循环可以被替换为stringsIt.forEachRemaining(s -> System.out.printf("line: %s%n", s));,顺便说一句 :) - Alexis C.
确实,我的错。我已经修正了我的回答。 - Olivier Grégoire
2
Spliterator方法非常简洁! - assylias
@assylias 我不确定(如果我确定的话,我会重新打开的)。这两个QA非常相似,但是这里有forEachRemaining使其更完整。请按照您认为最好的方式进行操作。 - Denys Séguret
1
问题和答案本质上是相同的,所以我会保持这样。 - assylias
显示剩余4条评论

1

由于你似乎只是在使用行,所以你可以从中获取Iterator; 注意,下面的代码假定流不为空:

final Iterator<String> iterator = theStream.iterator();

process(iterator.next());

iterator.forEachRemaining(doSomething());

1
我从一个流开始,而不是一个路径。 - Denys Séguret
@dystroy 请查看更新后的答案。 - fge

1
我认为你所描述的实际上是不可能的。即使是你发布的第一段代码,我也不建议使用。如果forEach以并行方式执行,你的if(first)可能无法正常工作。
如果持有数据的类是一个集合,你可以使用迭代器从列表中获取第一个。
如果你真的必须使用流,你可以这样做:
// assuming getStreamFromSomewhere recreates the Stream and is not
// very time consuming
getStreamFromSomewhere().limit(1).forEach(doFirst);
getStreamFromSomewhere().skip(1).forEach(doRest);

流是惰性的,因此它不会实际上两次遍历整个流。

重要的是要记住,Stream API本身并不持有任何数据。对Stream的任何调用更像是计划如何处理数据以及从源到目标的流程。随机访问不是其中的一部分。


1
这个的一个注意点是,你需要获得两个流 - 如果流的创建费时(例如:打开套接字或者在硬盘上查找文件),那么这种方式可能会比使用“迭代”相应方法更低效。 - assylias
@assylias 您的意思是它不能从流开始并返回子流吗? - Denys Séguret
3
不行,第一条语句执行完后,流将被关闭。 - Olivier Grégoire
@OlivierGrégoire 这意味着第二个流也会读取第一行,即使它跳过了它。对吗? - Florian Margaine
我的意思是,执行第二个语句时会出现“IllegalStateException”。 - Olivier Grégoire
显示剩余3条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接