我什么时候应该使用流?

新手上路,请多包涵

我刚刚在使用 List 及其 stream() 方法时遇到了一个问题。虽然我知道 如何 使用它们,但我不太确定 何时 使用它们。

例如,我有一个列表,其中包含通往不同位置的各种路径。现在,我想检查一个给定的路径是否包含列表中指定的任何路径。我想根据是否满足条件返回 boolean

当然,这本身并不是一项艰巨的任务。但是我不知道我应该使用流还是 for(-each) 循环。

名单

private static final List<String> EXCLUDE_PATHS = Arrays.asList(
    "my/path/one",
    "my/path/two"
);

使用流的示例:

 private boolean isExcluded(String path) {
    return EXCLUDE_PATHS.stream()
                        .map(String::toLowerCase)
                        .filter(path::contains)
                        .collect(Collectors.toList())
                        .size() > 0;
}

使用 for-each 循环的示例:

 private boolean isExcluded(String path){
    for (String excludePath : EXCLUDE_PATHS) {
        if (path.contains(excludePath.toLowerCase())) {
            return true;
        }
    }
    return false;
}

请注意, path 参数始终为 _小写_。

我的第一个猜测是 for-each 方法更快,因为如果满足条件,循环会立即返回。而流仍然会遍历所有列表条目以完成过滤。

我的假设正确吗?如果是这样, _为什么_(或者说 _什么时候_)我会使用 stream() 那么?

原文由 mcuenez 发布,翻译遵循 CC BY-SA 4.0 许可协议

阅读 402
2 个回答

你的假设是正确的。您的流实现比 for 循环慢。

这个流的使用应该和 for 循环一样快:

 EXCLUDE_PATHS.stream()
    .map(String::toLowerCase)
    .anyMatch(path::contains);

这将遍历项目,将 String::toLowerCase 和过滤器逐个应用于项目,并 在第一个匹配的项目处终止

collect() & anyMatch() 都是终端操作。 anyMatch() 在找到第一个项目时退出,而 collect() 需要处理所有项目。

原文由 Stefan Pries 发布,翻译遵循 CC BY-SA 4.0 许可协议

是否使用 Streams 的决定不应由性能考虑驱动,而应由可读性驱动。当真正涉及性能时,还有其他考虑因素。

使用你的 .filter(path::contains).collect(Collectors.toList()).size() > 0 方法,你正在处理所有元素并将它们收集到一个临时的 List ,在比较大小之前,仍然,这对于由两个元素组成的 Stream 几乎无关紧要。

使用 .map(String::toLowerCase).anyMatch(path::contains) 可以节省 CPU 周期和内存,如果你有大量的元素。尽管如此,这会将每个 String 转换为其小写表示,直到找到匹配项。显然,使用

private static final List<String> EXCLUDE_PATHS =
    Stream.of("my/path/one", "my/path/two").map(String::toLowerCase)
          .collect(Collectors.toList());

private boolean isExcluded(String path) {
    return EXCLUDE_PATHS.stream().anyMatch(path::contains);
}

反而。因此,您不必在每次调用 isExcluded 时重复转换为小写字母。如果 EXCLUDE_PATHS 中的元素数量或者字符串的长度变得非常大,你可以考虑使用

private static final List<Predicate<String>> EXCLUDE_PATHS =
    Stream.of("my/path/one", "my/path/two").map(String::toLowerCase)
          .map(s -> Pattern.compile(s, Pattern.LITERAL).asPredicate())
          .collect(Collectors.toList());

private boolean isExcluded(String path){
    return EXCLUDE_PATHS.stream().anyMatch(p -> p.test(path));
}

将字符串编译为带有 LITERAL 标志的正则表达式模式,使其行为就像普通的字符串操作一样,但允许引擎花费一些时间进行准备,例如使用 Boyer Moore 算法,以便在它执行时更高效来到实际比较。

当然,这只有在有足够的后续测试来补偿准备所花费的时间时才有回报。确定是否会是这种情况是实际性能考虑因素之一,除了第一个问题是此操作是否对性能至关重要。不是使用 Streams 还是 for 循环的问题。

顺便说一句,上面的代码示例保留了您原始代码的逻辑,这对我来说看起来很可疑。 Your isExcluded method returns true , if the specified path contains any of the elements in list, so it returns true for /some/prefix/to/my/path/one , as以及 my/path/one/and/some/suffix 甚至 /some/prefix/to/my/path/one/and/some/suffix

甚至 dummy/path/onerous 也被认为满足标准,因为它 contains 字符串 my/path/one

原文由 Holger 发布,翻译遵循 CC BY-SA 3.0 许可协议

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题