专业的JAVA编程教程与资源

网站首页 > java教程 正文

你真的知道Java中的String字符串拼接优化策略么?

temp10 2024-10-18 13:50:04 java教程 10 ℃ 0 评论

在实际开发中,经常会遇到字符串拼接相关的操作,例如我们在编写动态SQL的时候,需要对SQL语句进行拼接,而SQL语句就可以看做是一个字符串的拼接操作,而在之前的分享中,我们知道String类型的对象是一个不可变的对象,也就是说SQL语句的拼接其实是需要创建一个新的String对象的。那么下面我们就来看看Java中对于字符串拼接操作有哪些注意的地方。

字符串拼接操作

我们常用的字符串拼接操作就是通过 “+” 来进行连接,如下所示。

你真的知道Java中的String字符串拼接优化策略么?

String a = "Hello";
String b = "World!";
String str = a + b;

但是实际上,在编译的时候JDK会将上面这段代码编译成如下的这个操作。

String str = new StringBuilder().append(a).append(b).toString();

可以看出,拼接字符串使用了append()方法。该方法中调用了其父类AbstractStringBuilder的append()方法。

@Override
public StringBuilder append(String str) {
    super.append(str);
    return this;
}
// 父类的 append方法
public AbstractStringBuilder append(String str) {
    if (str == null)
        return appendNull();
    int len = str.length();
    ensureCapacityInternal(count + len);
    str.getChars(0, len, value, count);
    count += len;
    return this;
}

从源码中可以看到最终调用了它本身的一个getChars()方法,而这个方法内部最终调用的还是System.arraycopy()方法。

测试"+"和append()的性能

下面我们就来测试一下通过 “+” 和通过append() 来进行字符串拼接的操作性能。添加JMH的方式在之前的分享中已经提到过了,这里就不再重复编写了。测试类代码如下。

@State(Scope.Benchmark)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
public class TestString {
    String a = "Hello";
    String b = "World!";
    @Benchmark
    public String sttring(){
        String str = a + b;
        return str;
    }

    @Benchmark
    public String appendStr(){
        String str = new StringBuilder().append(a).append(b).toString();
        return str;
    }
}

主方法测试类如下所示。

public class Test {
    public static void main(String[] args) throws RunnerException {
        Options opt = new OptionsBuilder()
                .include(TestString.class.getSimpleName())
                .warmupBatchSize(5)
                .measurementIterations(1)
                .mode(Mode.AverageTime)
                .forks(1)
                .build();
        new Runner(opt).run();
    }
}

测试结果

Benchmark             Mode  Cnt   Score   Error  Units
TestString.appendStr  avgt       13.825          ns/op
TestString.sttring    avgt       13.918          ns/op

从测试结果来看,通过"+"和append的方式来拼接字符串,其效果基本上是一样的,从结果上来看差异性不是太大。这是因为在JVM进行编译的时候会对上述的两段代码都进行优化,可以使用虚拟机参数-XX:+OptimizeStringConcat 来开启字符串优化操作,在高版本的JDK中默认是开启状态,但是在JDK1.6以下,这个操作是默认关闭的。这里笔者使用的是Java11版本,所以默认是开启的。

打破规则的操作?

在默认情况下,-XX:+OptimizeStringConcat配置项是开启的,而这个配置项所起到的作用就是对字符串拼接操作进行优化。但是有一种情况却无法被这个参数所识别。可以在上述代码中加入如下的一个操作。

@Benchmark
public String appendS(){
    StringBuilder sb = new StringBuilder();
    sb.append(a);
    sb.append(b);
    return sb.toString();
}

加入上述操作之后,我们再来查看三者的操作性能有差区别。

Benchmark             Mode  Cnt   Score   Error  Units
TestString.appendS    avgt       24.669          ns/op
TestString.appendStr  avgt       13.802          ns/op
TestString.sttring    avgt       13.909          ns/op

从最终结果来看,通过上面这种方式来进行字符串拼接操作,并没有用到JIT优化。这是因为在append()的底层用到的也是System.arraycopy()操作。

当然与StringBuilder相同的还有StringBuffer,与StringBuilder一样,StringBuffer也继承了AbstractStringBuilder类,唯一不同的是StringBuffer的append()方法上加上了synchronized关键字,也就是说它在并发场景下是线程安全的。但是在一般的情况下,很少出现并发去修改字符串的操作,所以一般很少用到StringBuffer。下面我们来比较一下StringBuffer和StringBuilder的性能。

StringBuffer和StringBuilder性能比较

编写如下的测试代码

@State(Scope.Benchmark)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
public class TestString {
    String a = "Hello";
    String b = "World!";

    @Benchmark
    public String appendStringBuilder(){
        String str = new StringBuilder().append(a).append(b).toString();
        return str;
    }

    @Benchmark
    public String appendStringBuffer(){
        String str = new StringBuffer().append(a).append(b).toString();
        return str;
    }
}

测试结果

Benchmark                       Mode  Cnt   Score   Error  Units
TestString.appendStringBuffer   avgt       14.295          ns/op
TestString.appendStringBuilder  avgt       14.326          ns/op

从测试结果来看,二者的性能也没有什么区别。这是因为在虚拟机进行逃逸分析的时候,可能会消除对StringBuffer上的锁操作。可以通过如下的参数来配置。

开启逃逸分析
-XX:+DoEscapeAnalysis
锁消除
-XX:+EliminateLocks

总结

从上面的分析,可以看到,在一般情况下程序都会得到JVM的优化来提升执行性能,但是并不能说明所有的代码都会得到优化,例如上面描述的StringBuilder的第二种情况。

对开发者来讲,并不能一直依赖于自动代码优化,所以在开发的时候要尽量注意对于字符串的拼接做操作上的优化,尤其对于一些涉及字符类型转化的场景中,可能会涉及到字符串的转码,以及字符串的类型转换等操作。所以在使用的时候,一定要注意。从逻辑的角度上来对有些代码操作进行优化。

本文暂时没有评论,来添加一个吧(●'◡'●)

欢迎 发表评论:

最近发表
标签列表