我什么时候应该使用溪流?

时间:2017-02-27 12:59:31

标签: java java-8 java-stream

我在使用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()呢?

5 个答案:

答案 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,就会短路。