【问题标题】:How optimized are Java 8 stream filters over collection methods?Java 8 流过滤器对收集方法的优化程度如何?
【发布时间】:2017-06-01 03:51:25
【问题描述】:

例如我有一个逗号分隔的字符串:

String multiWordString= "... , ... , ... ";

我想检查另一个字符串 str 是否存在于 csv 字符串中。 然后我可以做以下两件事:

1.

boolean contains = Arrays.asList(multiWordString.split(",")).contains(str);

2.

boolean contains = Arrays.asList(multiWordString.split(",")).stream().filter(e -> e.equals(str)).findFirst();

编辑:示例字符串恰好使用逗号作为分隔符。我应该为示例字符串使用更好的名称以避免混淆。 我更新了名字。在这个问题中,我试图找出使用 Java 8 流和循环/收集方法之间的性能差异。

【问题讨论】:

  • 看看这个有用的link
  • 这里不能用一个好的正则表达式吗?
  • Obi wan:取决于 csv 内容。但是是的,正则表达式会起作用,正如我几个小时前回答的那样:-)

标签: java collections lambda java-8


【解决方案1】:

没有测试就无法判断,内部细节可能会改变一种解决方案对另一种解决方案的作用,因此最好的方法是衡量。众所周知,流有点慢 - 它们背后确实有基础设施......

这是一个天真的简单测试(数据很少):

@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@Warmup(iterations = 5, time = 2, timeUnit = TimeUnit.SECONDS)
@Measurement(iterations = 5, time = 2, timeUnit = TimeUnit.SECONDS)
@State(Scope.Benchmark)
public class CSVParsing {
    public static void main(String[] args) throws RunnerException {
        Options opt = new OptionsBuilder().include(CSVParsing.class.getSimpleName())
                .jvmArgs("-ea")
                .shouldFailOnError(true)
                .build();
        new Runner(opt).run();
    }

    @Param(value = { "a,e, b,c,d",
            "a,b,c,d, a,b,c,da,b,c,da,b,c,da,b,c,da,b,c,da,b,c,da,b,c,da,b,c,d, e",
            "r, m, n, t,r, m, n, tr, m, n, tr, m, n, tr, m, n, tr, m, n, tr, m, n, tr, m, n, t, e" })
    String csv;

    @Fork(1)
    @Benchmark
    public boolean containsSimple() {
        return Arrays.asList(csv.split(",")).contains("e");
    }

    @Fork(1)
    @Benchmark
    public boolean containsStream() {
        return Arrays.asList(csv.split(",")).stream().filter(e -> e.equals("e")).findFirst().isPresent();
    }

    @Fork(1)
    @Benchmark
    public boolean containsStreamParallel() {
        return Arrays.asList(csv.split(",")).stream().filter(e -> e.equals("e")).findFirst().isPresent();
    }
}

即使您不理解代码,结果也是可以比较的简单数字:

 CSVParsing.containsSimple   (first Parameter)    181.201 ±   5.390
 CSVParsing.containsStream                        255.851 ±   5.598
 CSVParsing.containsStreamParallel                295.296 ±  57.800

我不会显示其余的结果(对于其他参数),因为它们在同一范围内。

底线是它们确实不同,最多 100 ns;让我重申一下:纳秒

确实有区别;但是如果你真的很关心这个差异,那么 csv 解析可能首先是错误的选择。

【讨论】:

  • 请注意,您的每个测试都包含Arrays.asList(csv.split(",")) 的基本成本,这些成本与流与收集操作无关。除非您添加另一个测试,Pattern.compile(",").splitAsStream(csv),否则会显示没有中间数组/集合的真正流操作……
  • @Holger 是的,但是他们有这个成本。该成本包含在每个测试中,因此使所有测试均等地倾斜。我想为每个基准测试方法只提供array of Strings,但这会使代码更加复杂。
  • 当然,这些费用都包含在内。但只要你不知道这些基本成本与你所比较的成本之间的比率,结果就毫无意义。可能containsSimplecontainsStream之间的比率确实大致是181:255,但也有可能基本成本是180containsSimplecontainsStream之间的比率是1:75。谁知道?
  • @Holger 确实!但我根本没有考虑这个,只测试了OP提供的代码。不过有点不清楚。你的意思是应该有另一个测试Pattern.compile(",").splitAsStream(csv)。这个不会创建数组,但会引入 Pattern 匹配成本。
  • 准备工作会引入一种不同的基本成本,另一方面,它不仅会跳过数组创建,而且只会匹配所需数量的,,以便在其中找到匹配的字符串使用findFirst 时的链式filter 操作。对于包含匹配项的大字符串,这可以得到很大的回报。由于总结果取决于输入,添加另一个单独的测试会很有用。
【解决方案2】:

注意,CSV 通常比逗号的分隔字符串更复杂,还有转义逗号需要担心。我希望这是一个示例,或者不是正在导入的 CSV 格式。

你不应该先从数组转换为列表,直接使用 Arrays.stream 或 Stream.of() 从数组转换为流

但是流很懒惰,它们只做他们需要做的工作。

.contains(str) 将在找到匹配项后立即中止。

不测量就很难判断性能,所以现在让程序正确且易于维护。

如果性能是一个问题,在完成一些工作后,分析并查看哪些部分可能会更好,尝试替代方案,然后选择获胜者。

【讨论】:

    【解决方案3】:

    两个版本的工作“量”相同:需要创建一个列表并且需要比较所有元素。

    选项 2 增加了在幕后设置很多东西的开销。因此,与选项 1 相比,选项 2 消耗的 CPU 周期要多得多。流不是免费的!

    效率在这里有不同的方面。当您的 csv 输入相对简单时,正则表达式可能会很好(检查是否可以在 csv 字符串中找到某些模式。当您必须处理任意 csv 输入(例如,包含引号逗号的值)时,那么用逗号简单的分割无论如何都会导致不正确的结果。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-07-08
      • 1970-01-01
      相关资源
      最近更新 更多