使用 Java 流优化机会

2022-09-04 22:21:59

我正在查看一些代码,并遇到了这种方法,该方法采用HTML Header值(即Content-Disposition=inline;filename=foo.bar),并将其解析为由分号分隔的映射,以键=值对分隔。起初,它看起来像是使用流进行优化的良好候选者,但是在我实现它之后,我无法重用计算的String.indexOf('=')值的事实意味着字符串必须扫描3次,这实际上不如原始值最佳。我非常清楚,在很多情况下,Streams不是这项工作的正确工具,但我想知道我是否错过了一些技术,这些技术可以使Stream与初始代码一样高性能/更高性能。

  /**
   * Convert a Header Value String into a Map
   *
   * @param value The Header Value
   * @return The data Map
   */
  private static Map<String,String> headerMap (String value) {
    int eq;
    Map<String,String> map = new HashMap<>();
    for(String entry : value.split(";")) {
      if((eq = entry.indexOf('=')) != -1) {
        map.put(entry.substring(0,eq),entry.substring(eq + 1));
      }
    }
    return map;

    return Stream.of(value.split(";")).filter(entry -> entry.indexOf('=') != -1).collect(Collectors.));
  } //headerMap

我尝试流式传输它:

  /**
   * Convert a Header Value String into a Map
   *
   * @param value The Header Value
   * @return The data Map
   */
  private static Map<String,String> headerMap (String value) {
    return Stream.of(value.split(";")).filter(entry -> entry.indexOf('=') != -1).collect(Collectors.toMap(entry -> entry.substring(0,entry.indexOf('=')),entry -> entry.substring(entry.substring(entry.indexOf('=') + 1))));
  } //headerMap

答案 1

此解决方案仅查找一次:'='

private static Map<String, String> headerMap(String value) {
    return Stream.of(value.split(";"))
            .map(s -> s.split("=", 2))
            .filter(arr -> arr.length == 2)
            .collect(Collectors.toMap(arr -> arr[0], arr -> arr[1]));
}

请注意,此处使用 的快速路径,因此实际上并未创建正则表达式。String.split

请注意,使用番石榴,即使在Java-8之前,您也可以以非常干净的方式执行此操作:

private static Map<String, String> headerMap(String value) {
    return Splitter.on( ';' ).withKeyValueSeparator( '=' ).split( value );
}

一般来说,我建议你不要手动解析HTTP标头。这里有很多警告。例如,请参阅如何在Apache HTTP库中实现它。使用库。


答案 2

我想出了以下代码:

private static Map<String, String> headerMap(String value) {
    return Stream.of(value.split(";"))
                .filter(entry -> entry.indexOf('=') != -1)
                .map(entry -> {
                    int i = entry.indexOf('=');
                    return new String[] { entry.substring(0, i), entry.substring(i + 1) };
                })
                .collect(Collectors.toMap(array -> array[0], array -> array[1]));
}

它仅扫描两次,方法是将键和值存储在大小为 2 的数组中。我不确定它是否会像循环一样高性能,因为我们正在创建另一个对象来充当持有者。entryfor

另一个扫描唯一一次的解决方案是这个,尽管我不是很了解它:entry

private static Map<String, String> headerMap(String value) {
    return Stream.of(value.split(";"))
            .map(entry -> {
                int i = entry.indexOf('=');
                if (i == -1) {
                    return null;
                }
                return new String[] { entry.substring(0, i), entry.substring(i + 1) };
            })
            .filter(Objects::nonNull)
            .collect(Collectors.toMap(array -> array[0], array -> array[1]));
}

我实现了一个JMH基准测试来测试这一点。以下是基准代码:

@Warmup(iterations = 5, time = 1000, timeUnit = TimeUnit.MILLISECONDS)
@Measurement(iterations = 10, time = 1000, timeUnit = TimeUnit.MILLISECONDS)
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.MICROSECONDS)
@Fork(3)
@State(Scope.Benchmark)
public class StreamTest {

    private static final String VALUE = "Accept=text/plain;"
        + "Accept-Charset=utf-8;"
        + "Accept-Encoding=gzip, deflate;"
        + "Accept-Language=en-US;"
        + "Accept-Datetime=Thu, 31 May 2007 20:35:00 GMT;"
        + "Cache-Control=no-cache;"
        + "Connection=keep-alive;"
        + "Content-Length=348;"
        + "Content-Type=application/x-www-form-urlencoded;"
        + "Date=Tue, 15 Nov 1994 08:12:31 GMT;"
        + "Expect=100-continue;"
        + "Max-Forwards=10;"
        + "Pragma=no-cache";

    @Benchmark
    public void loop() {
        int eq;
        Map<String, String> map = new HashMap<>();
        for (String entry : VALUE.split(";")) {
            if ((eq = entry.indexOf('=')) != -1) {
                map.put(entry.substring(0, eq), entry.substring(eq + 1));
            }
        }
    }

    @Benchmark
    public void stream1() {
        Stream.of(VALUE.split(";"))
        .filter(entry -> entry.indexOf('=') != -1)
        .map(entry -> {
            int i = entry.indexOf('=');
            return new String[] { entry.substring(0, i), entry.substring(i + 1) };
        })
        .collect(Collectors.toMap(array -> array[0], array -> array[1]));
    }

    @Benchmark
    public void stream2() {
        Stream.of(VALUE.split(";"))
        .map(entry -> {
            int i = entry.indexOf('=');
            if (i == -1) {
                return null;
            }
            return new String[] { entry.substring(0, i), entry.substring(i + 1) };
        })
        .filter(Objects::nonNull)
        .collect(Collectors.toMap(array -> array[0], array -> array[1]));
    }

    public static void main(String[] args) throws Exception {
         Main.main(args);
    }

}

这是结果(代码i5 3230M CPU @ 2.60 GHz,Windows 10,Oracle JDK 1.8.0_25):

Benchmark           Mode  Cnt  Score   Error  Units
StreamTest.loop     avgt   30  1,541 ± 0,038  us/op
StreamTest.stream1  avgt   30  1,633 ± 0,042  us/op
StreamTest.stream2  avgt   30  1,604 ± 0,058  us/op

这表明,流解决方案和 for 循环在性能方面实际上是等效的。


推荐