何时应使用流?

2022-08-31 09:22:09

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

例如,我有一个列表,其中包含指向不同位置的各种路径。现在,我想检查单个给定路径是否包含列表中指定的任何路径。我想根据是否满足条件返回 a。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()


答案 1

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

不过,此流使用速度应与 for 循环一样快:

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

这将循环访问项目,逐个应用和筛选项目,并在匹配的第一个项目处终止String::toLowerCase

两者都是终端操作。 但是,在第一个找到的项处退出,同时要求处理所有项。collect()anyMatch()anyMatch()collect()


答案 2

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

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

使用可以节省 CPU 周期和内存,如果您有大量元素。尽管如此,这会将每个转换为其小写表示形式,直到找到匹配项。显然,使用是有道理的.map(String::toLowerCase).anyMatch(path::contains)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);
}

相反。因此,您不必在每次调用时重复转换为小写。如果字符串中的元素数或长度变得非常大,则可以考虑使用isExcludedEXCLUDE_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));
}

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

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

顺便说一句,上面的代码示例保留了原始代码的逻辑,这在我看来是有问题的。如果指定的路径包含 list 中的任何元素,则您的方法返回 ,因此它将返回 、以及 甚至 。isExcludedtruetrue/some/prefix/to/my/path/onemy/path/one/and/some/suffix/some/prefix/to/my/path/one/and/some/suffix

甚至被认为是满足标准的,因为它是字符串...dummy/path/onerouscontainsmy/path/one


推荐