使用Java流优化商机
我浏览了一些代码,发现了这个方法,它接受一个HTML标题值(即Content Disposition=inline;filename=foo.bar),并将其解析为一个由分号into key=value对分隔的映射。起初,它看起来像是使用流进行优化的一个很好的候选者,但在我实现它之后,我无法重用计算的字符串。indexOf('=')值意味着必须扫描字符串3次,这实际上不如原始字符串优化。我非常清楚,在很多情况下,流不是适合此工作的工具,但我想知道我是否错过了一些技术,这些技术可以使流的性能与初始代码相同/更高使用Java流优化商机,java,java-8,java-stream,Java,Java 8,Java Stream,我浏览了一些代码,发现了这个方法,它接受一个HTML标题值(即Content Disposition=inline;filename=foo.bar),并将其解析为一个由分号into key=value对分隔的映射。起初,它看起来像是使用流进行优化的一个很好的候选者,但在我实现它之后,我无法重用计算的字符串。indexOf('=')值意味着必须扫描字符串3次,这实际上不如原始字符串优化。我非常清楚,在很多情况下,流不是适合此工作的工具,但我想知道我是否错过了一些技术,这些技术可以使流的性能与初始
/**
* 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
/**
*将标题值字符串转换为映射
*
*@param value标题值
*@返回数据地图
*/
私有静态映射头映射(字符串值){
国际均衡器;
Map Map=newhashmap();
for(字符串条目:value.split(;)){
if((eq=entry.indexOf('='))!=-1){
map.put(entry.substring(0,eq),entry.substring(eq+1));
}
}
返回图;
返回Stream.of(value.split(“;”).filter(条目->条目.indexOf('=')!=-1.collect(收集器));
}//头映射
我尝试将其流式传输:
/**
* 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
/**
*将标题值字符串转换为映射
*
*@param value标题值
*@返回数据地图
*/
私有静态映射头映射(字符串值){
返回Stream.of(value.split(“;”).filter(entry->entry.indexOf('=')!=-1).collect(Collectors.toMap(entry->entry.substring(0,entry.indexOf('=')),entry->entry.substring(entry.indexOf('=')+1));
}//头映射
我想出了以下代码:
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]));
}
我实现了一个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);
}
}
这表明streams解决方案和for循环在性能方面实际上是等效的。此解决方案只查找一次
'='
:
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]));
}
一般来说,我建议您不要手动解析HTTP头。这里有很多警告。例如,请参见ApacheHTTP库中的内容。使用库。一般来说,流可以使代码更短,并允许您更轻松地并行化,但一般来说,您不应该期望流可以使代码更快。同意,但使用流可能会降低代码速度,只是为了节省一点代码空间,不建议这样做。在大多数情况下,您可以通过精心设计的流链获得速度(或至少不会失去速度)。我希望在我的尝试中错过了一些更为精通的人能够指出的东西。事实上,我不确定我是否接受这两种说法中的任何一种——我非常乐意接受一些减速来换取代码可读性的提高,但我不确定流是否能够带来速度的提高(除了简化并行化)。溪流只是一个图书馆;JIT对它们进行了很好的优化,但如果它们优于经过良好调优的命令式代码,我至少会有点惊讶。而且您的原始解析代码似乎太脆弱,缺少许多关键案例。这是针对此类标题的,但在其中添加额外的对象创建可能会抵消删除字符串扫描的好处。尤其是由于HTTP头字符串的大小通常相当短;我想这样会更好。然后并行处理每个元素。哇,做得很好。“我从来没用过那个工具,我一定要试试看。”克里斯登内特,在这种情况下,拆分器有什么优势?我看不出有什么好处。你可以提前读取整个字符串,并在不需要的时候生成一个字符串数组。很好!我必须将它添加到@Tunaki的JMH基准测试中,并对它们进行正面测试。我实际上是将此作为优化机会的示例,而不是解析HTTP头的正确方法。
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
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]));
}
private static Map<String, String> headerMap(String value) {
return Splitter.on( ';' ).withKeyValueSeparator( '=' ).split( value );
}