【发布时间】:2017-10-09 20:17:36
【问题描述】:
如何对单个流的元素执行多个不相关的操作?
假设我有一个由文本组成的List<String>。列表中的每个字符串可能包含也可能不包含某个单词,它表示要执行的操作。比方说:
- 如果字符串包含'of',则该字符串中的所有单词都必须计算在内
- 如果字符串包含 'for',则必须返回第一次出现 'for' 之后的部分,从而生成包含所有子字符串的
List<String>
当然,我可以这样做:
List<String> strs = ...;
List<Integer> wordsInStr = strs.stream()
.filter(t -> t.contains("of"))
.map(t -> t.split(" ").length)
.collect(Collectors.toList());
List<String> linePortionAfterFor = strs.stream()
.filter(t -> t.contains("for"))
.map(t -> t.substring(t.indexOf("for")))
.collect(Collectors.toList());
但随后列表将被遍历两次,如果strs 包含大量元素,这可能会导致性能下降。
是否可以在不遍历列表两次的情况下以某种方式执行这两个操作?
【问题讨论】:
-
IMO,完全没有必要因为不存在的性能问题而使您的代码复杂化。即使它很大,在内存中迭代列表/集合也非常快。同样,IMO,如果我是在您的代码审查中做出决定的人,我将接受 OP 中的代码并拒绝接受答案中的代码。
-
@123-xyz 已经一针见血了。有趣的是,当你说
contains("for")时,你并不关心迭代两次,紧接着是indexOf("for"),尽管这些操作是相关的 并且可以在不牺牲可读性的情况下融合。如果你真的关心性能,想想t.split(" ")在你想要的时候实际上做了什么,就是计算空格的数量。当操作不相关时,迭代两次不是问题。 -
也许我应该更准确地表述问题。我试图在帖子中放一个示例,因此我必须编写一个简单的用例。这个用例根本没有意义,而且实现起来也很糟糕:确实,应用
t.split(" ")只得到实际上is 不好的字数。但是,如果我没有包含元素的列表,而是从网络连接或某些 I/O 通道获得的Stream,那会显着改变用例,不是吗? -
@MCEmperor 是的,它可能会改变一些事情 - 如果您需要将它用于多个不相关的操作,则取决于将其收集到集合中的 Stream 的大小可能仍然值得内存成本,或者它可能完全不可能(例如,如果流是无限的——当然,在这种情况下,所有不相关的操作都必须是短路的,这两种方法都可能非常复杂)。
-
@MCEmperor 如果元素来自 network/db 调用,那将是一个完全不同的问题/场景。首先,即使大小很小或只有一个,我们也不应该重复 network/db 调用。其次,您仍然可以将 network/db 调用的结果保存到临时列表中,然后执行您在 OP 中所做的操作。第三,如果尺寸太大而无法保存或由于某些原因不想保存,可以在映射函数中使用
Pair/Triple:Function<? super T, Pair<R1, R2>> mapper = ...,然后再开始考虑创建一些复杂的收集器。
标签: java java-stream