StringBuilder高性能用法总结

项目开发中很多时候我们都需要拼接字符串,那如何才能高效的完成字符串拼接呢?

指定初始容量

先来看一下StringBuilder的源码(JDK7)

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

    /** use serialVersionUID for interoperability */
    static final long serialVersionUID = 4383685877147921099L;

    /**
     * Constructs a string builder with no characters in it and an
     * initial capacity of 16 characters.
     */
    public StringBuilder() {
        super(16);
    }

    /**
     * Constructs a string builder with no characters in it and an
     * initial capacity specified by the capacity argument.
     *
     * @param      capacity  the initial capacity.
     * @throws     NegativeArraySizeException  if the capacity
     *               argument is less than 0.
     */
    public StringBuilder(int capacity) {
        super(capacity);
    }
}

StringBuilder的默认构造方法调用的是父类AbstractStringBuilder 中的AbstractStringBuilder(int capacity)构造方法,如下:

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

    /**
     * The count is the number of characters used.
     */
    int count;

    /**
     * This no-arg constructor is necessary for serialization of subclasses.
     */
    AbstractStringBuilder() {
    }

    /**
     * Creates an AbstractStringBuilder of the specified capacity.
     */
    AbstractStringBuilder(int capacity) {
        value = new char[capacity];
    }
}


StringBuilder的内部有一个char[], 在调用StringBuilder的无参构造方法时其内部char[]的默认长度是16。当我们调用StringBuilder的append方法时,其实就是不断的往char[]里填东西的过程。

public StringBuilder append(String str) {
    super.append(str);
    return this;
}

其中,super.append是调用AbstractStringBuilder 的append(String str)方法,如下:

public AbstractStringBuilder append(String str) {
    if (str == null) str = "null";
    int len = str.length();
    ensureCapacityInternal(count + len);
    str.getChars(0, len, value, count);
    count += len;
    return this;
}

StringBuilder的扩容和ArrayList有些类似,具体代码如下:

/**
 * This method has the same contract as ensureCapacity, but is
 * never synchronized.
 */
private void ensureCapacityInternal(int minimumCapacity) {
    // overflow-conscious code
    if (minimumCapacity - value.length > 0)
        expandCapacity(minimumCapacity);
}

/**
 * This implements the expansion semantics of ensureCapacity with no
 * size check or synchronization.
 */
void expandCapacity(int minimumCapacity) {
    int newCapacity = value.length * 2 + 2;
    if (newCapacity - minimumCapacity < 0)
        newCapacity = minimumCapacity;
    if (newCapacity < 0) {
        if (minimumCapacity < 0) // overflow
            throw new OutOfMemoryError();
        newCapacity = Integer.MAX_VALUE;
    }
    value = Arrays.copyOf(value, newCapacity);
}

StringBuilder默认长度是16,然后,如果要append第17个字符,怎么办?
答案是采用 Arrays.copyOf()成倍复制扩容!
扩容的性能代价是很严重的:一来有数组拷贝的成本,二来原来的char[]也白白浪费了要被GC掉。可以想见,一个129字符长度的字符串,经过了16,32,64, 128四次的复制和丢弃,合共申请了496字符的数组,在高性能场景下,这几乎不能忍。

由此可见,合理设置一个初始值多重要。使用之前先仔细评估一下要保存的字符串最大长度。

复用StringBuilder

StringBuilder.setLength()方法只重置它的count指针,而char[]则会继续重用,源码如下:

public void setLength(int newLength) {
    if (newLength < 0)
        throw new StringIndexOutOfBoundsException(newLength);
    ensureCapacityInternal(newLength);

    if (count < newLength) {
        for (; count < newLength; count++)
            value[count] = '\0';
    } else {
        count = newLength;
    }
}

toString()方法:

public String toString() {
    // Create a copy, don't share the array
    return new String(value, 0, count);
}

而toString()时会把当前的count指针也作为参数传给String的构造函数,所以不用担心把超过新内容大小的旧内容也传进去了。可见,StringBuilder是完全可以被重用的。

具体示例如下:

String[] history_steps = ruleDetailInfo.getHistory_steps().split(",");
for (String step : history_steps){
    sb.setLength(0);
    sb.append(ruleDetailInfo.getBusiness_id()).append("\t").append(step);
    results.add(sb.toString());
}

+ 与 StringBuilder的区别

String s = "hello" + user.getName();

这一行代码经过javac编译后的效果,的确等价于使用StringBuilder,但没有设定长度。

String s = new StringBuilder().append(“hello”).append(user.getName());

但是,如果像下面这样:

 String s = “hello ”;
// 中间插入了其他一些代码
s = s + user.getName();

每一条语句,都会生成一个新的StringBuilder,这里就有了两个StringBuilder,性能就完全不一样了。

如果是在循环体里s+=i; 就更加多得没谱,例如:

String str = "";
for(int i=0; i<10000;i++){
    str += i;
}

StringBuffer 与 StringBuilder区别

StringBuffer的源码如下(JDK7):

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

    /** use serialVersionUID from JDK 1.0.2 for interoperability */
    static final long serialVersionUID = 3388685877147921107L;

    public StringBuffer() {
        super(16);
    }

    public StringBuffer(int capacity) {
        super(capacity);
    }

    public StringBuffer(String str) {
        super(str.length() + 16);
        append(str);
    }

    public StringBuffer(CharSequence seq) {
        this(seq.length() + 16);
        append(seq);
    }

    public synchronized int length() {
        return count;
    }

    public synchronized int capacity() {
        return value.length;
    }

    public synchronized void ensureCapacity(int minimumCapacity) {
        if (minimumCapacity > value.length) {
            expandCapacity(minimumCapacity);
        }
    }

    public synchronized void setLength(int newLength) {
        super.setLength(newLength);
    }

    public synchronized StringBuffer append(Object obj) {
        super.append(String.valueOf(obj));
        return this;
    }

    public synchronized StringBuffer append(String str) {
        super.append(str);
        return this;
    }
    ......
}

StringBuilder源码:

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

    /** use serialVersionUID for interoperability */
    static final long serialVersionUID = 4383685877147921099L;

    public StringBuilder() {
        super(16);
    }

    public StringBuilder(int capacity) {
        super(capacity);
    }

    public StringBuilder(String str) {
        super(str.length() + 16);
        append(str);
    }

    public StringBuilder(CharSequence seq) {
        this(seq.length() + 16);
        append(seq);
    }

    public StringBuilder append(Object obj) {
        return append(String.valueOf(obj));
    }

    public StringBuilder append(String str) {
        super.append(str);
        return this;
    }

    // Appends the specified string builder to this sequence.
    private StringBuilder append(StringBuilder sb) {
        if (sb == null)
            return append("null");
        int len = sb.length();
        int newcount = count + len;
        if (newcount > value.length)
            expandCapacity(newcount);
        sb.getChars(0, len, value, count);
        count = newcount;
        return this;
    }

    public StringBuilder append(StringBuffer sb) {
        super.append(sb);
        return this;
    }
    ......
}

StringBuffer与StringBuilder都是继承于AbstractStringBuilder,唯一的区别就是StringBuffer的函数上都有synchronized关键字。

小结

StringBuilder是非线程安全的,所以不能在多线程环境下共享使用。StringBuilder在使用的时候一定要指定其初始大小,另外,对性能要求比较高的场景下,可以考虑用一个ThreadLocal 缓存可重用的StringBuilder。

你可能感兴趣的:(Java)