Java下String和StringBuilder的append性能解析

源代码是万物之源。——黑客帝国

先看下String的源代码:

public final class String
    implements java.io.Serializable, Comparable, CharSequence {
    /** The value is used for character storage. */
    private final char value[];//char数组也就是String的值

    /** 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就是一个char[]。唯一需要注意的点应该就是final修饰符,也就意味着value是常量不可更改。这个和之前object-c中的设计很像,但不知道在这里是不是为了线程安全。从后面replace等函数的实现也可以看出,更改String的值需要重新申请一个String变量。

    public String replace(char oldChar, char newChar) {
        if (oldChar != newChar) {
            int len = value.length;
            int i = -1;
            char[] val = value; /* avoid getfield opcode */

            while (++i < len) {
                if (val[i] == oldChar) {
                    break;
                }
            }
            if (i < len) {
                char buf[] = new char[len];
                for (int j = 0; j < i; j++) {
                    buf[j] = val[j];
                }
                while (i < len) {
                    char c = val[i];
                    buf[i] = (c == oldChar) ? newChar : c;
                    i++;
                }
                return new String(buf, true);
            }
        }
        return this;
    }

/* avoid getfield opcode */
关于这个注释,查了一下。得到的答案是将变量复制到局部变量中,这样在下面的循环操作中就可以不用反复的从堆中取数据了。(栈中的访问速度更快,那是不是操作次数比较多的变量都可以用这种方法去增加速度呢?)

但是在这个文件夹中我没有找到+号的重载函数(当然java本身不允许重载运算符),经过百度发现这个加号的重载是在编译阶段实现的。

//以下两者是等价的
s = i + ""
s = String.valueOf(i);
//以下两者也是等价的
= "abc" + i;
= new StringBuilder("abc").append(i).toString();

再看下StringBuilder的源代码:

  /**
     * 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() {
    }

可变的char[],以及长度。

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

这里对于为什么要在初始化的时候预留一个16个大小的数组还不太明白。

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

这里就不贴所有的代码了,主要就是如果是对象则会调用string的valueof()转成String,如果是String就是调用getchars(),再对char[]添加赋值。
所以其性能差异即在少做了一步string和StringBuilder的转化。
而String s = "abc";这样的操作会在常量字符区生成一个"abc"常量,也会增加开销。

java,StringBuilder预留16位

    public AbstractStringBuilder append(String str) {
        if (str == null)
            return appendNull();
        int len = str.length();
        ensureCapacityInternal(count + len);//在添加String时确定内部空间足够
        str.getChars(0, len, value, count);
        count += len;
        return this;
    }
    private void ensureCapacityInternal(int minimumCapacity) {
        // overflow-conscious code
        if (minimumCapacity - value.length > 0)//如果超过了预留的空间大小,则选择扩容。
            expandCapacity(minimumCapacity);
    }
    void expandCapacity(int minimumCapacity) {
        int newCapacity = value.length * 2 + 2;//将其扩充为2倍的大小加2;
        if (newCapacity - minimumCapacity < 0)
            newCapacity = minimumCapacity;//如果还不够大小,则将空间扩充为两个字符串大小之和
        if (newCapacity < 0) {
            if (minimumCapacity < 0) // overflow
                throw new OutOfMemoryError();
            newCapacity = Integer.MAX_VALUE;//如果字符串之和小于int最大值,但是两倍太大会导致overflow,则将其设置为int最大值
        }
        value = Arrays.copyOf(value, newCapacity);
    }

这样做最大的好处应该是在扩展小的字符串时不用每次都申请空间,只有在原有空间已满的时候再进行扩容,典型的空间换时间。

你可能感兴趣的:(Java下String和StringBuilder的append性能解析)