Java下String和StringBuilder的append
源代码是万物之源。——黑客帝国
先看下String的源代码:
public final class String
implements java.io.Serializable, Comparable<String>, 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);
}
这样做最大的好处应该是在扩展小的字符串时不用每次都申请空间,只有在原有空间已满的时候再进行扩容,典型的空间换时间。