字符串拼接之String、StringBuilder、StringBuffer

字符串拼接之String、StringBuilder、StringBuffer

在写Java代码的过程中,我们经常会用到字符串的拼接,但是你知道他们的区别?废话不多说,直接上测试代码。

一. 效率比较

1. String拼接字符串的代码

    public static String testString(int appendTimes) {
        String str = "";

        if (appendTimes < 0) {
            return str;
        }
        long begin = System.currentTimeMillis();
        while (appendTimes-- > 0) {
            str += "s";
        }
        System.out.println("String time: " + (System.currentTimeMillis() - begin));
        return str;
    }

2. StringBuilder拼接字符串的代码

    public static String testStringBuilder(int appendTimes) {
        StringBuilder buffer = new StringBuilder("");
        if (appendTimes < 0) {
            buffer.toString();
        }
        long begin = System.currentTimeMillis();
        while (appendTimes-- > 0) {
            buffer.append("s");
        }
        System.out.println("StringBuilder time: " + (System.currentTimeMillis() - begin));
        return buffer.toString();
    }

3. StringBuffer拼接字符串的代码

    public static String testStringBuffer(int appendTimes) {
        StringBuffer buffer = new StringBuffer("");
        if (appendTimes < 0) {
            buffer.toString();
        }
        long begin = System.currentTimeMillis();
        while (appendTimes-- > 0) {
            buffer.append("s");
        }
        System.out.println("StringBuffer time: " + (System.currentTimeMillis() - begin));
        return buffer.toString();
    }

4. 测试代码

    public static void main(String[] args) {
        int times = 1000;

        StringJoinExample.testString(times);
        StringJoinExample.testStringBuilder(times);
        StringJoinExample.testStringBuffer(times);
    }

5. 测试结果

我们分别用三种拼接方式进行10次、10000次、1000000次字符拼接,结果如下(时间单位:ms):

//拼接10次结果
String time: 0
StringBuilder time: 0
StringBuffer time: 0
//拼接10000次结果
String time: 109
StringBuilder time: 1
StringBuffer time: 1
//拼接1000000次结果
String time: 223065
StringBuilder time: 14
StringBuffer time: 20

从测试结果上看,进行10次拼接时:三种方式用时都很少;进行10000次拼接时:String耗时远远超过了StringBuilder 和StringBuffer;进行1000000次拼接时:String性能令人担忧,用了近4分钟,StringBuilder 和StringBuffer却相差无几;
进而可以得出这样的结论:在进行高频率的字符串拼接时,三种方式的效率顺序为:StringBuilder > StringBuffer > String

二. 原因剖析

1. String

String类在JDK中的源码如下:

public final class String
    implements java.io.Serializable, Comparable<String>, CharSequence
{
    /** The value is used for character storage. */
    private final char value[];

    /** The offset is the first index of the storage that is used. */
    private final int offset;

    /** The count is the number of characters in the String. */
    private final int count;

    /** Cache the hash code for the string */
    private int hash; // Default to 0

    /** use serialVersionUID from JDK 1.0.2 for interoperability */
    private static final long serialVersionUID = -6849794470754667710L;

    ........
}

从源码中可以看出,String是一个final类,并且是用一个final修饰的char型数组来存放字符串的。Java中一个变量被final修饰,初始化赋值后,再次赋值就编译器就会报错,因此这里需要记住一点:“String对象一旦被创建就是固定不变的了,对String对象的任何改变都不影响到原对象,相关的任何change操作都会生成新的对象”。(有困惑的小伙伴可以看一下:深入理解Java中的String)对于以下简单代码:

        String s1 = "s";
        s1 += "abc";

编译器实则是new了一个新的String对象,赋值为“sabc”,然后修改s1的引用。原来的对象则需要被GC(垃圾回收器回收)。

2. StringBuilder和StringBuffer

查看JDK StringBuilder和StringBuffer两个类源码:

public final class StringBuilder
    extends AbstractStringBuilder
    implements java.io.Serializable, CharSequence
{
    ......
}

 public final class StringBuffer
    extends AbstractStringBuilder
    implements java.io.Serializable, CharSequence
{
    ......
}

从上面可以看出:StringBuilder和StringBuffer继承了同一个父类AbstractStringBuilder,我们在查看AbstractStringBuilder JDK中的源码:

abstract class AbstractStringBuilder implements Appendable, CharSequence {
    /**
     * The value is used for character storage.
     */
    char[] value;

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

    public void getChars(int srcBegin, int srcEnd, char dst[], int dstBegin) {
        if (srcBegin < 0) {
            throw new StringIndexOutOfBoundsException(srcBegin);
        }
        if (srcEnd > value.length) {
            throw new StringIndexOutOfBoundsException(srcEnd);
        }
        if (srcBegin > srcEnd) {
            throw new StringIndexOutOfBoundsException(srcEnd - srcBegin);
        }
        System.arraycopy(value, srcBegin, dst, dstBegin, srcEnd - srcBegin);
    }
}

根据源码可知:AbstractStringBuilder用一个char型value[]存放字符内容,其append方法实际上的做个两关键操作,1)对value[]进行扩容;2)将新增字符串复制到value[]尾部,并不是像String那样新建一个对象,String效率低下的主要原因是在不停地new对象。

StringBuilder和StringBuffer性能上的差距是在于StringBuffer 大部分方法(append也不例外)都加了synchronized关键字,同步是需要消耗一些计算资源的,因此StringBuffer相对于StringBuilder会稍微慢一些,但是StringBuffer中这些加了synchronized的方法是线程安全的,有兴趣的朋友可以看一下JDK源码。

参考文章:

  1. 深入理解Java中的String
发布了17 篇原创文章 · 获赞 9 · 访问量 6529

猜你喜欢

转载自blog.csdn.net/liuyanglglg/article/details/82084771