2017-02-27 153 views
58

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

+9

流比传统的for循环更富有表现力和可读性。在后面你需要小心if-then和条件等的内在性。流表达式非常清晰:将文件名转换为小写,然后过滤某些内容,然后计算,收集等结果:非常迭代计算流程的表达。 –

+8

这里不需要'new String [] {...}'。只需使用'Arrays.asList(“my/path/one”,“my/path/two”)' – Holger

+0

你是对的,但我打算展示我要处理的数据结构,它是一个String [ ]'。 – mcuenez

回答

58

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

该物流用法应尽可能快地进行环路虽然:

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

此遍历的项目,施加String::toLowerCase和过滤程序以项一个接一个和在第一终止项目匹配。

Both collect() & anyMatch()是终端操作。 anyMatch()退出第一个找到的项目,虽然,collect()需要处理所有项目。

+1

太棒了,不知道'findFirst()'与filter()'结合使用。显然,我不知道如何使用流以及我认为的。 – mcuenez

+4

网络上有一些非常有趣的博客文章和演示文稿,关于流API性能,我发现这对于理解这些东西如何在引擎盖下工作非常有帮助。如果你对此感兴趣,我绝对可以推荐你研究一下。 –

+0

编辑之后,我觉得你的回答是应该被接受的答案,因为你也在其他答案的评论中回答了我的问题。虽然,我想给@ rvit34发布代码一些功劳:-) – mcuenez

18

是的。你是对的。您的流式方法会产生一些开销。但是,你可以使用这样的结构:

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

的主要原因使用流却是它们让你的代码更简单,易于阅读。

+2

'anyMatch'是'filter(...)。findFirst()。isPresent()'的快捷方式吗? – mcuenez

+5

是的!这比我的第一个建议还要好。 –

5

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

如果我们想要性能,我们应该使用parallelStream,它是专为。一般而言,序列号较慢。

有阅读有关ForLoop, Stream and ParallelStream Performance的好文章。

在您的代码中,我们可以使用终止方法停止第一次匹配的搜索。 (anyMatch ...)

+3

请注意,对于小流和其他情况,由于启动成本,并行流可能会变慢。如果你有一个有序的终端操作,而不是一个无序的可并行化操作,那么在最后重新同步。 – CAD97

29

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

有了您的.filter(path::contains).collect(Collectors.toList()).size() > 0方法,你处理所有的元素,并将它们收集到一个临时List,比较大小之前,不过,这很少事项由两个元素的流。

使用.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 ...

+0

对可能的性能优化有很好的见解,谢谢。关于你的回答的最后一部分:如果我对你的评论的回复不令人满意,请将我的示例代码视为仅仅帮助其他人理解我所要求的 - 而不是实际的代码。另外,如果你有一个更好的例子,你总是可以编辑这个问题。 – mcuenez

+2

我接受你的意见,这个操作是你真正想要的,所以没有必要改变它。我只是为未来的读者保留最后一部分,所以他们知道这不是一个典型的操作,而且,它已经被讨论过了,不需要进一步的评论...... – Holger

相关问题