我应该多次流式传输还是在一个流中完成所有计算?

9

I have the following code:

mostRecentMessageSentDate = messageInfoList
    .stream()
    .findFirst().orElse(new MessageInfo())
    .getSentDate();

unprocessedMessagesCount = messageInfoList
    .stream()
    .filter(messageInfo -> messageInfo.getProcessedDate() == null)
    .count();

hasAttachment = messageInfoList
    .stream()
    .anyMatch(messageInfo -> messageInfo.getAttachmentCount() > 0);

如您所见,我流式传输相同的列表3次,因为我想找到3个不同的值。如果我在For-Each循环中执行此操作,则只需循环一次。

从性能上讲,使用for循环进行这样的操作是否更好,以便只循环一次?我发现流更易读。

编辑:我进行了一些测试:

import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

public class Main {

public static void main(String[] args) {

    List<Integer> integerList = populateList();

    System.out.println("Stream time: " + timeStream(integerList));
    System.out.println("Loop time: " + timeLoop(integerList));

}

private static List<Integer> populateList() {
    return IntStream.range(0, 10000000)
            .boxed()
            .collect(Collectors.toList());
}

private static long timeStream(List<Integer> integerList) {
    long start = System.currentTimeMillis();

    Integer first = integerList
            .stream()
            .findFirst().orElse(0);

    long containsNumbersGreaterThan10000 = integerList
            .stream()
            .filter(i -> i > 10000)
            .count();

    boolean has10000 = integerList
            .stream()
            .anyMatch(i -> i == 10000);

    long end = System.currentTimeMillis();

    System.out.println("first: " + first);
    System.out.println("containsNumbersGreaterThan10000: " + containsNumbersGreaterThan10000);
    System.out.println("has10000: " + has10000);

    return end - start;
}

private static long timeLoop(List<Integer> integerList) {
    long start = System.currentTimeMillis();

    Integer first = 0;
    boolean has10000 = false;
    int count = 0;
    long containsNumbersGreaterThan10000 = 0L;
    for (Integer i : integerList) {
        if (count == 0) {
            first = i;
        }

        if (i > 10000) {
            containsNumbersGreaterThan10000++;
        }

        if (!has10000 && i == 10000) {
            has10000 = true;
        }

        count++;
    }

    long end = System.currentTimeMillis();

    System.out.println("first: " + first);
    System.out.println("containsNumbersGreaterThan10000: " + containsNumbersGreaterThan10000);
    System.out.println("has10000: " + has10000);

    return end - start;
}
}

正如预期的那样,for循环始终比流(streams)更快。

first: 0
containsNumbersGreaterThan10000: 9989999
has10000: true
Stream time: 57
first: 0
containsNumbersGreaterThan10000: 9989999
has10000: true
Loop time: 38

但是从未有显著的差异。

findFirst可能不是一个好的例子,因为如果流为空,它只会退出,但我想知道它是否有影响。

我希望能够从一个流中进行多个计算。IntSummaryStatistics并不能完全满足我的需求。我想我会听从@florian-schaetz的建议,更注重可读性,即使这只会带来微小的性能提升。


1
"过早优化是万恶之源。除非你知道这段代码需要优化,否则不要试图优化你的代码。在这种情况下,取决于你的列表大小、附件分布、调用频率等等因素。但我猜99%的人都不需要对像这样的代码进行优化,因为速度提升最多只能是微不足道的。如果你需要优化它,不要强迫自己使用流。如果不需要,而且它们使代码更难读懂……那就不要使用它们。" - Florian Schaetz
1
你可能想要查看 IntSummaryStatisticsCollectors.summarizingInt,它们可以做类似的事情,在流上一次收集三个不同的信息。但是,除非你的列表非常大,否则这可能不值得。 - David Conrad
1个回答

5

您不需要三次迭代整个集合。

mostRecentMessageSentDate = messageInfoList
        .stream()
        .findFirst().orElse(new MessageInfo())
        .getSentDate();

以上代码检查集合中是否有任何元素,并根据此返回一个值。它不需要遍历整个集合。
unprocessedMessagesCount = messageInfoList
        .stream()
        .filter(messageInfo -> messageInfo.getProcessedDate() == null)
        .count();

这个需要过滤掉所有没有处理日期的元素,并对它们进行计数,因此这个要遍历整个集合。
hasAttachment = messageInfoList
        .stream()
        .anyMatch(messageInfo -> messageInfo.getAttachmentCount() > 0);

上述内容只需遍历元素,直到找到带有附件的消息为止。
因此,在这三个流中,只需要一个流在最坏情况下遍历整个集合,你可能需要迭代两次(第二次和潜在的第三个流)。
使用常规的 For-Each 循环可能会更有效率,但你真的需要吗?如果你的集合只包含少量对象,我不会费心进行优化。
然而,使用传统的 For-Each 循环,可以将最后两个流组合起来。
int unprocessedMessagesCount = 0;
boolean hasAttachment = false;

for (MessageInfo messageInfo: messageInfoList) {
  if (messageInfo.getProcessedDate() == null) {
    unprocessedMessagesCount++;
  }
  if (hasAttachment == false && messageInfo.getAttachmentCount() > 0) {
    hasAttachment = true;
  }
}

如果你觉得这是更好的解决方案(我也认为流更易读),那就由你决定。我没有找到将这三个流合并为一个的方法,至少没有更易读的方式。


1
我收藏了这个问题,因为我希望有一种与流相关的方式,可以同时执行2个只读操作,而不使用for each循环或forEach语法。 - Ryan Leach
1
@RyanTheLeach 是的,但正如我在答案中所说,我没有看到一种好的、可读的方法来实现它。 - Magnilex
4
也许可以通过滥用“map”或类似的方式使用流来编写它,但我非常怀疑这是否是一种好方法。检查一下您的代码是否真正需要运行时优化,否则请记住:过早地进行优化是万恶之源——在这种情况下,您会牺牲可读性来解决一个不存在的问题。只有当它确实是一个性能问题时,重构它,此时@ Magnilex的解决方案似乎比我能想到的任何流版本都更易读。 - Florian Schaetz
感谢@Magnilex的回答。我希望能够在同一流中进行多个计算(请参见我的编辑)。 - Somaiah Kumbera

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