JDK 的 String 类式java日常开发中最常用的类,但是因为设计时间过早,其中隐含了不少问题,没有做到最好的性能体验。
今天我们要聊的就是String.replace() 方法。
String.replcae() vs StringUtils.replace()
StringUtils 是apache commons lang 中的工具包,这个库是目前业界使用最广泛,历史最悠久的String工具库。
插入mvn排行图
整个mvn依赖库中,排行第九,core库中排行第二。这就是它的实力。
性能对比
这里我们使用JMH 这个java届最权威的框架来进行性能测试,我们在用例中准备了6组测试case,大家感兴趣的话,可以自行运行。
本文测试环境基于jdk8u_312.
package com.moyucoding.test;
import org.apache.commons.lang3.StringUtils;
import org.openjdk.jmh.annotations.Benchmark;
import org.openjdk.jmh.annotations.Fork;
import org.openjdk.jmh.annotations.Measurement;
import org.openjdk.jmh.annotations.Warmup;
import org.openjdk.jmh.infra.Blackhole;
@Fork(value = 3, jvmArgsAppend = "-Djmh.stack.lines=3")
@Warmup(iterations = 5)
@Measurement(iterations = 7)
public class StringReplaceBenchmark {
private static final String SHORT_STRING_NO_MATCH = "abc";
private static final String SHORT_STRING_ONE_MATCH = "a'bc";
private static final String SHORT_STRING_SEVERAL_MATCHES = "'a'b'c'";
private static final String LONG_STRING_NO_MATCH =
"abcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabc";
private static final String LONG_STRING_ONE_MATCH =
"abcabcabcabcabcabcabcabcabcabcabca'bcabcabcabcabcabcabcabcabcabcabcabcabc";
private static final String LONG_STRING_SEVERAL_MATCHES =
"abcabca'bcabcabcabcabcabc'abcabcabca'bcabcabcabcabcabca'bcabcabcabcabcabcabc";
public static void main(String[] args) throws Exception {
org.openjdk.jmh.Main.main(args);
}
@Benchmark
public void testStringReplaceShortStringNoMatch(Blackhole blackhole) {
blackhole.consume(SHORT_STRING_NO_MATCH.replace("'", "''"));
}
@Benchmark
public void testStringReplaceLongStringNoMatch(Blackhole blackhole) {
blackhole.consume(LONG_STRING_NO_MATCH.replace("'", "''"));
}
@Benchmark
public void testStringReplaceShortStringOneMatch(Blackhole blackhole) {
blackhole.consume(SHORT_STRING_ONE_MATCH.replace("'", "''"));
}
@Benchmark
public void testStringReplaceLongStringOneMatch(Blackhole blackhole) {
blackhole.consume(LONG_STRING_ONE_MATCH.replace("'", "''"));
}
@Benchmark
public void testStringReplaceShortStringSeveralMatches(Blackhole blackhole) {
blackhole.consume(SHORT_STRING_SEVERAL_MATCHES.replace("'", "''"));
}
@Benchmark
public void testStringReplaceLongStringSeveralMatches(Blackhole blackhole) {
blackhole.consume(LONG_STRING_SEVERAL_MATCHES.replace("'", "''"));
}
@Benchmark
public void testStringUtilsReplaceShortStringNoMatch(Blackhole blackhole) {
blackhole.consume(StringUtils.replace(SHORT_STRING_NO_MATCH, "'", "''"));
}
@Benchmark
public void testStringUtilsReplaceLongStringNoMatch(Blackhole blackhole) {
blackhole.consume(StringUtils.replace(LONG_STRING_NO_MATCH, "'", "''"));
}
@Benchmark
public void testStringUtilsReplaceShortStringOneMatch(Blackhole blackhole) {
blackhole.consume(StringUtils.replace(SHORT_STRING_ONE_MATCH, "'", "''"));
}
@Benchmark
public void testStringUtilsReplaceLongStringOneMatch(Blackhole blackhole) {
blackhole.consume(StringUtils.replace(LONG_STRING_ONE_MATCH, "'", "''"));
}
@Benchmark
public void testStringUtilsReplaceShortStringSeveralMatches(Blackhole blackhole) {
blackhole.consume(StringUtils.replace(SHORT_STRING_SEVERAL_MATCHES, "'", "''"));
}
@Benchmark
public void testStringUtilsReplaceLongStringSeveralMatches(Blackhole blackhole) {
blackhole.consume(StringUtils.replace(LONG_STRING_SEVERAL_MATCHES, "'", "''"));
}
}
经过一个小时的测试后,我们得出的结果是
Benchmark Mode Cnt Score Error Units
StringReplaceBenchmark.testStringReplaceLongStringNoMatch thrpt 21 12210067.250 ± 342987.422 ops/s
StringReplaceBenchmark.testStringReplaceLongStringOneMatch thrpt 21 3984303.788 ± 74056.443 ops/s
StringReplaceBenchmark.testStringReplaceLongStringSeveralMatches thrpt 21 3181532.512 ± 74569.757 ops/s
StringReplaceBenchmark.testStringReplaceShortStringNoMatch thrpt 21 25666110.698 ± 1573992.322 ops/s
StringReplaceBenchmark.testStringReplaceShortStringOneMatch thrpt 21 11668173.747 ± 290415.490 ops/s
StringReplaceBenchmark.testStringReplaceShortStringSeveralMatches thrpt 21 6001214.622 ± 98091.136 ops/s
StringReplaceBenchmark.testStringUtilsReplaceLongStringNoMatch thrpt 21 49604380.534 ± 429136.912 ops/s
StringReplaceBenchmark.testStringUtilsReplaceLongStringOneMatch thrpt 21 13529127.012 ± 249110.511 ops/s
StringReplaceBenchmark.testStringUtilsReplaceLongStringSeveralMatches thrpt 21 10322497.588 ± 167876.297 ops/s
StringReplaceBenchmark.testStringUtilsReplaceShortStringNoMatch thrpt 21 267192070.389 ± 4515902.677 ops/s
StringReplaceBenchmark.testStringUtilsReplaceShortStringOneMatch thrpt 21 37803496.123 ± 548901.194 ops/s
StringReplaceBenchmark.testStringUtilsReplaceShortStringSeveralMatches thrpt 21 20540547.076 ± 395061.937 ops/s
这个结果显示,当前运行的是吞吐量模式,统计出来的结果就是 ops/s ,就是每秒能进行的操作量,这个值当然是越大越好。 这个结果不太方便看,所以我为你们贴心地准备了图表。
// 图表
从图表中可以一目了然得看到,在第四个测试用例中,StringUtils 取得了10倍于String 的性能优势。
需要注意的是,这个结果仅针对 jdk8u_312,其他版本的结果:
- jdk9的String.replace 方法和StringUtils.replace 持平;
- jdk13中String.replace 方法比StringUtils.replace 快30%.
考虑到大多数项目和公司依然还在jdk8 ,甚至更低的版本,将String.replace 替换喂 StringUtils.replace 是完全有益的:
- 减少cpu使用
- 使应用程序响应速度更快
StringUtils 更快的原因
关键在于第3807行,进行其他更重的操作时,会进进行indexOF查找,看看对应的字符串是否匹配,否则直接结束。
JDK9 之所以能和StringUtils 一样,也是因为做了同样的优化
在项目中替换
只需要简单地把"String.replace" 换成 "StringUtils.replace" 即可获得十倍的性能提升。
比如参考下面这个方法
url = url.replace(domain, "bytedance").replace("https://", "http://");
替换后
url = StringUtils.replace(url,domain,"bytedance");
url = StringUtils.replace(url,"https://", "http://");