我在使用List
及其stream()
方法时遇到了一个问题。虽然我知道 如何使用它们,但我不太确定何时才能使用它们。
例如,我有一个列表,其中包含指向不同位置的各种路径。现在,我想检查单个给定路径是否包含列表中指定的任何路径。我想根据条件是否符合来返回boolean
。
这当然不是一项艰巨的任务。但我想知道我是应该使用流还是使用for(-each)循环。
列表
private static final List<String> EXCLUDE_PATHS = Arrays.asList(new String[]{
"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()
呢?
答案 0 :(得分:68)
你的假设是正确的。您的流实现比for循环慢。
此流使用应该与for循环一样快:
EXCLUDE_PATHS.stream()
.map(String::toLowerCase)
.anyMatch(path::contains);
这会遍历这些项目,将String::toLowerCase
和过滤器逐个应用于项目,并将终止于匹配的第一项。
collect()
&amp; anyMatch()
是终端操作。不过,anyMatch()
会在第一个找到的项目中退出,而collect()
则需要处理所有项目。
答案 1 :(得分:30)
是否使用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
循环的问题。
顺便说一下,上面的代码示例保留了原始代码的逻辑,这对我来说是个问题。如果指定的路径包含列表中的任何元素,则isExcluded
方法会返回true
,因此它会返回true
/some/prefix/to/my/path/one
以及my/path/one/and/some/suffix
或甚至是/some/prefix/to/my/path/one/and/some/suffix
。
即使dummy/path/onerous
被视为符合条件contains
字符串my/path/one
...
答案 2 :(得分:19)
呀。你是对的。您的流方法将有一些开销。但你可以使用这样的结构:
private boolean isExcluded(String path) {
return EXCLUDE_PATHS.stream().map(String::toLowerCase).anyMatch(path::contains);
}
使用流的主要原因是它们使您的代码更简单易读。
答案 3 :(得分:8)
Java中的流的目标是简化编写并行代码的复杂性。它受到函数式编程的启发。串行流只是为了使代码更清晰。
如果我们想要性能,我们应该使用parallelStream,它被设计为。一般来说,序列号较慢。
有一篇好文章可以阅读 ForLoop
, Stream
and ParallelStream
Performance。
在您的代码中,我们可以使用终止方法停止第一场比赛的搜索。 (anyMatch ...)
答案 4 :(得分:0)
正如其他人提到的许多优点一样,但是我只想在流评估中提及惰性评估。当我们map()
创建小写路径流时,我们并没有立即创建整个流,而是延迟构造了该流,这就是为什么性能应该等同于传统的for循环。它没有进行完整扫描,map()
和anyMatch()
同时执行。 anyMatch()
一旦返回true,就会短路。