什么时候应该使用stream?

当我使用一个List及其stream()方法时,我遇到了一个问题。 虽然我知道如何使用它们,但我不清楚何时使用它们。

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

这当然不是一个艰巨的任务本身。 但是我想知道我应该使用stream还是for( – 每个)循环。

列表

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

示例 – stream

 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方法更快,因为如果满足条件,循环将立即返回。 尽pipestream仍然会遍历所有列表条目以完成过滤。

我的假设是否正确? 如果是这样, 为什么 (或者说何时 )我会使用stream()呢?

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

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

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

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

collect()anyMatch()都是terminal操作。 anyMatch()退出第一个find的项目,虽然collect()需要处理所有项目。

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

使用.filter(path::contains).collect(Collectors.toList()).size() > 0方法,您将处理所有元素并将它们收集到临时List ,然后比较大小,对于由两个元素组成的stream而言是重要的。

使用.map(String::toLowerCase).anyMatch(path::contains)可以节省CPU周期和内存,如果你有大量的元素。 不过,这将每个String转换为小写forms,直到find匹配。 很明显,使用中有一点

 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将转换重复为isExcluded 。 如果EXCLUDE_PATHS中元素的数量或者string的长度变得很大,可以考虑使用

 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标志将string编译为正则expression式,使其像普通的string操作一样工作,但是允许引擎花费一些时间准备,例如使用Boyer Moorealgorithm,以便在实际比较时更高效。

当然,如果有足够的后续testing来弥补准备时间,这只会带来好处。 确定这是否是这种情况,是除了第一个问题,这个操作是否会对性能至关重要的实际性能考虑因素之一。 不是使用Streams还是for循环的问题。

顺便说一下,上面的代码示例保留了您的原始代码的逻辑,这看起来是值得怀疑的。 如果指定的path包含列表中的任何元素,则isExcluded方法将返回true ,因此它会为/some/prefix/to/my/path/one以及my/path/one/and/some/suffix返回true甚至/some/prefix/to/my/path/one/and/some/suffix

甚至dummy/path/onerous被认为是符合标准,因为它containsstringmy/path/one

是啊。 你是对的。 你的stream方法会有一些开销。 但是你可以使用这样的结构:

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

使用stream的主要原因是它们让你的代码更简单易读。

Java中的stream的目标是简化编写并行代码的复杂性。 它受函数式编程的启发。 串行stream只是为了使代码更清洁。

如果我们想要性能,我们应该使用parallelStream,这是专为。 一般而言,串行的速度较慢。

有一篇关于ForLoopStreamParallelStream性能的好文章。

在你的代码中,我们可以使用终止方法来停止第一场比赛的search。 (anyMatch …)