java四种字符串拼接方式性能分析

常见的四种字符串拼接方法分别是 
1.直接用“+”号 
2.使用String的方法concat 
3.使用StringBuilder的append 
4.使用StringBuffer的append

字符串拼接我分为了两种拼接方式,一种是同一个字符串大量循环拼接,第二种是大量不同的字符串每次都拼接固定数量的字符串。第二种更接近与服务器上的使用场景,因为工程上是很少有一个字符串不断拼接的,基本都是大量字符串按同一种模式拼接(比如构造上报参数)。代码如下:

public class StringConcat {
    public static void main(String[] args) {
        plus();
        concat();
        stringBuffer();
        stringBuilder();
    }

    public static void plus(){
        String initial = "";
        long start = System.currentTimeMillis();
        for(int i = 0; i < 100000; i++){
            initial = initial + "a";
        }
        long end = System.currentTimeMillis();
        System.out.println("plus:" + (end - start));

        start = System.currentTimeMillis();
        for(int i = 0; i < 100000; i++){
            String a = "a";
            a = a + String.valueOf(i);
            //a = a + String.valueOf(i) + String.valueOf(i) + String.valueOf(i) + String.valueOf(i) + String.valueOf(i) + String.valueOf(i) + String.valueOf(i) + String.valueOf(i);
        }
        end = System.currentTimeMillis();
        System.out.println("double plus:" + (end - start));
    }

    public static void stringBuilder(){
        StringBuilder initial = new StringBuilder("");
        long start = System.currentTimeMillis();
        for(int i = 0; i < 100000; i++){
            initial = initial.append("b");
        }
        long end = System.currentTimeMillis();
        System.out.println("StringBuilder:" + (end - start));

        start = System.currentTimeMillis();
        for(int i = 0; i < 100000; i++){
            StringBuilder b = new StringBuilder("b");
            b.append(String.valueOf(i))
            //b.append(String.valueOf(i)).append(String.valueOf(i)).append(String.valueOf(i)).append(String.valueOf(i)).append(String.valueOf(i)).append(String.valueOf(i)).append(String.valueOf(i)).append(String.valueOf(i));
        }
        end = System.currentTimeMillis();
        System.out.println("double StringBuilder:" + (end - start));
    }

    public static void stringBuffer(){
        StringBuffer initial = new StringBuffer("");
        long start = System.currentTimeMillis();
        for(int i = 0; i < 100000; i++){
            initial = initial.append("c");
        }
        long end = System.currentTimeMillis();
        System.out.println("StringBuffer:" + (end - start));

        start = System.currentTimeMillis();
        for(int i = 0; i < 100000; i++){
            StringBuffer c = new StringBuffer("c");
            c.append(String.valueOf(i));
            //c.append(String.valueOf(i)).append(String.valueOf(i)).append(String.valueOf(i)).append(String.valueOf(i)).append(String.valueOf(i)).append(String.valueOf(i)).append(String.valueOf(i)).append(String.valueOf(i));
        }
        end = System.currentTimeMillis();
        System.out.println("double StringBuffer:" + (end - start));
    }

    public static void concat(){
        String initial = "";
        long start = System.currentTimeMillis();
        for(int i = 0; i < 100000; i++){
            initial = initial.concat("d");
        }
        long end = System.currentTimeMillis();
        System.out.println("concat:" + (end - start));

        start = System.currentTimeMillis();
        for(int i = 0; i < 100000; i++){
            String d = "d";
            d = d.concat(String.valueOf(i));
            d = //d.concat(String.valueOf(i)).concat(String.valueOf(i)).concat(String.valueOf(i)).concat(String.valueOf(i)).concat(String.valueOf(i)).concat(String.valueOf(i)).concat(String.valueOf(i)).concat(String.valueOf(i));
        }
        end = System.currentTimeMillis();
        System.out.println("double concat:" + (end - start));
    }
}

结果如下: 

è¿éåå¾çæè¿°
结果:从结果可以看出,在拼接10万次的情况下,循环拼接同一个字符串的时候,StringBuilder和StringBuffer的优势是巨大的,仅仅花了不到10ms的时间,而StringBuilder稍快一点。而直接用“+”号和concat都花费了秒级的时间。但是在对10万字符串都拼接一个串的时候,concat的情况发生了逆转,速度甚至比StringBuilder和StringBuffer更快。

 

原理分析

1.加号拼接 
打开编译后的字节码我们可以发现加号拼接字符串jvm底层其实是调用StringBuilder来实现的,也就是说”a” + “b”等效于下面的代码片。

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


但并不是说直接用“+”号拼接就可以达到StringBuilder的效率了,因为用“+”号每拼接一次都会新建一个StringBuilder对象,并且最后toString()方法还会生成一个String对象。在循环拼接十万次的时候,就会生成十万个StringBuilder对象,十万个String对象,这简直就是噩梦。

2.concat拼接 
concat的源代码如下,可以看到,concat其实就是申请一个char类型的buf数组,将需要拼接的字符串都放在这个数组里,最后再转换成String对象。

public String concat(String str) {
        int otherLen = str.length();
        if (otherLen == 0) {
            return this;
        }
        int len = value.length;
        char buf[] = Arrays.copyOf(value, len + otherLen);
        str.getChars(buf, len);
        return new String(buf, true);
    }

3.StringBuilder/StringBuffer 
这两个类实现append的方法都是调用父类AbstractStringBuilder的append方法,只不过StringBuffer是的append方法加了sychronized关键字,因此是线程安全的。append代码如下,他主要也是利用char数组保存字符,通过ensureCapacityInternal方法来保证数组容量可用还有扩容。

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;
    }

他扩容的方法的代码如下,可见,当容量不够的时候,数组容量右移1位(也就是翻倍)再加2,以前的jdk貌似是直接写成int newCapacity = (value.length * 2) + 2,后来优化成了右移,可见,右移的效率还是比直接乘更高的。

private int newCapacity(int minCapacity) {
        // overflow-conscious code
        int newCapacity = (value.length << 1) + 2;
        if (newCapacity - minCapacity < 0) {
            newCapacity = minCapacity;
        }
        return (newCapacity <= 0 || MAX_ARRAY_SIZE - newCapacity < 0)
            ? hugeCapacity(minCapacity)
            : newCapacity;
    }

 

原因分析

1.循环拼接字符串 
通过实验我们发现,在循环拼接同一个字符串的时候,他们效率的按快慢排序是 
StringBulider > StringBuffer >> String.concat > “+”。 
StringBulider比StringBuffer更快这个容易理解,因为StringBuffer的方法是sychronized修饰的,同步的时候会损耗掉一些性能。StringBulider和String.concat的区别,主要在扩容上,String.concat是需要多少扩多少,而StringBulider是每次翻两倍,指数级扩容。在10万次拼接中,String.concat需要扩容10万次,而StringBuilder只需要扩容log100000次(大约17次),除此之外,concat每次都会生成一个新的String对象,而StringBuilder则不必,那StringBuilder如此快就不难解释了。至于直接用“+”号连接,之前已经说了,它会产生大量StringBuilder和String对象,当然就最慢了。 
2.大量字符串拼接 
在只拼接少量字符串的情况下的时候,他们效率的按快慢排序是 
String.concat > StringBulider > StringBuffer > “+”。 
为什么在拼接少量字符串的时候,String.concat就比StringBuilder快了呢,原因大致有两点,一是StringBuilder的调用栈更深,二是StringBuilder产生的垃圾对象更多,并且它重写的toString方法为了做到不可变性采用了“保护性拷贝”,因此效率不如concat。 
详细原因分析参考:concat和StringBuilder性能分析 
保护性拷贝见:保护性拷贝 
当拼接的字符串少的时候,concat因为上述优势略快,但是当一次性拼接字符串多的时候,StringBuilder的扩容更少优势便会开始展现出来,例如一次拼接8个字符串,concat的效率就已经明显不如StringBuilder了,如下图。 

ä¸æ¬¡æ¼æ¥8个å­ç¬¦ä¸²


结论

从以上分析我们可以得出以下几点结论 
1.无论如何直接用“+”号连接字符串都是最慢的 
2.在拼接少数字符串(不超过4个)的时候,concat效率是最高的 
3.多个字符串拼接的时候,StringBuilder/StringBuffer的效率是碾压的 
4.在不需要考虑线程安全问题的时候,使用StringBuilder的效率比StringBuffer更高

 

你可能感兴趣的:(理论知识)