美文网首页
StringBuilder高性能用法总结

StringBuilder高性能用法总结

作者: FX_SKY | 来源:发表于2017-04-02 10:55 被阅读855次

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

    指定初始容量

    先来看一下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 <code>capacity</code> argument.
         *
         * @param      capacity  the initial capacity.
         * @throws     NegativeArraySizeException  if the <code>capacity</code>
         *               argument is less than <code>0</code>.
         */
        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。

    相关文章

      网友评论

          本文标题:StringBuilder高性能用法总结

          本文链接:https://www.haomeiwen.com/subject/mgqgottx.html