String
public final class String implements Serializable, Comparable<String>, CharSequence {
private final char[] value;
......
}
final修饰的String 类,以及final修饰的char[] value,表示String类不可被继承,且value只能被初始化一次。这里的value变量其实就是存储了String字符串中的所有字符。
String a = new String("aa"):代表在堆内存中创建了一个字符串对象,变量a指向该对象,而该对象又指向常量池中的字符串常量aa。
String b = "bb":代表变量b直接指向常量池中的字符串常量bb,不会在堆内存中创建对象。
public String substring(int beginIndex) {
if (beginIndex < 0) {
throw new StringIndexOutOfBoundsException(beginIndex);
}
int subLen = value.length - beginIndex;
if (subLen < 0) {
throw new StringIndexOutOfBoundsException(subLen);
}
return (beginIndex == 0) ? this : new String(value, beginIndex, subLen);
}
public String concat(String str) {
int otherLen = str.length();
if (otherLen == 0) {
return this;
}
int len = value.length;
char buf[] = Arrays.copyOf(value, len + otherLen);
str.getChars(buf, len);
return new String(buf, true);
}
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;
}
我们可以看到,String类的substring方法,concat方法,replace方法,都是内部重新生成一个String对象的。这也就是为什么我们如果采用String对象频繁的进行拼接,截取,替换操作效率很低下的原因。
StringBuilder
public final class StringBuilder
extends AbstractStringBuilder
implements java.io.Serializable, CharSequence {
......
}
StringBuilder类继承AbstractStringBuilder抽象类,其中StringBuilder的大部分方法都是直接调用的父类的实现。
public StringBuilder() {
super(16);
}
public StringBuilder(int capacity) {
super(capacity);
}
public StringBuilder(String str) {
super(str.length() + 16);
append(str);
}
可以看出StringBuilder的默认初始容量是16,并且都调用了父类的构造方法。
public StringBuilder append(String str) {
super.append(str);
return this;
}
public StringBuilder delete(int start, int end) {
super.delete(start, end);
return this;
}
public StringBuilder insert(int offset, String str) {
super.insert(offset, str);
return this;
}
public int indexOf(String str) {
return super.indexOf(str);
}
public StringBuilder reverse() {
super.reverse();
return this;
}
再来看他的一些操作方法也都是调用了父类的实现,接下来我们就看看父类的具体实现。
abstract class AbstractStringBuilder implements Appendable, CharSequence {
char[] value;
int count;
AbstractStringBuilder() {}
AbstractStringBuilder(int capacity) {
value = new char[capacity];
}
}
char[] value没有final修饰,代表它是可以扩展的。接下来我们重点看一下append方法。
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;
}
首先判断是否为null,null也是可以append进去的。
private AbstractStringBuilder appendNull() {
int c = count;
ensureCapacityInternal(c + 4);
final char[] value = this.value;
value[c++] = 'n';
value[c++] = 'u';
value[c++] = 'l';
value[c++] = 'l';
count = c;
return this;
}
接着判断数组容量是否满足此次append,不满足的话执行扩容:尝试将新容量扩为大小变成2倍+2,如果不够,直接扩充到需要的容量大小,最大容量为MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8。
private void ensureCapacityInternal(int minimumCapacity) {
if (minimumCapacity - value.length > 0) {
value = Arrays.copyOf(value, newCapacity(minimumCapacity));
}
}
private int newCapacity(int minCapacity) {
//尝试将新容量扩为大小变成2倍+2,如果不够,直接扩充到需要的容量大小。
int newCapacity = (value.length << 1) + 2;
if (newCapacity - minCapacity < 0) {
newCapacity = minCapacity;
}
return (newCapacity <= 0 || MAX_ARRAY_SIZE - newCapacity < 0)
? hugeCapacity(minCapacity)
: newCapacity;
}
private int hugeCapacity(int minCapacity) {
if (Integer.MAX_VALUE - minCapacity < 0) { // overflow
throw new OutOfMemoryError();
}
return (minCapacity > MAX_ARRAY_SIZE)
? minCapacity : MAX_ARRAY_SIZE;
}
紧接着通过getChars调native方法getCharsNoCheck实现真正的append
public void getChars(int srcBegin, int srcEnd, char dst[], int dstBegin) {
//首先做一些参数校验,这里就省略了
......
getCharsNoCheck(srcBegin, srcEnd, dst, dstBegin);
}
@FastNative
native void getCharsNoCheck(int start, int end, char[] buffer, int index);
整个StringBuilder的append方法,本质上是调用System的native方法,直接将String 类型的str字符串中的字符数组,拷贝到了StringBuilder的字符数组中。
最后说下StringBuilder的toString方法:
@Override
public String toString() {
if (count == 0) {
return "";
}
return StringFactory.newStringFromChars(0, count, value);
}
这里的toString方法直接new 一个String对象,将StringBuilder对象的value进行一个拷贝,重新生成一个对象,不共享之前StringBuilder的char[]。
以上就是StringBuilder的拼接字符串的原理分析,可以发现没有像String一样去重新new 对象,所以在频繁的拼接字符上,StringBuilder的效率远远高于String类。
StringBuffer
public final class StringBuffer
extends AbstractStringBuilder
implements java.io.Serializable, CharSequence {
//transient关键字的作用:被修饰的成员属性变量不被序列化
private transient char[] toStringCache;
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);
}
}
其构造方法和成员基本和StringBuilder一样,唯一区别就是char[]不允许序列化。
@Override
public synchronized StringBuffer append(String str) {
toStringCache = null;
super.append(str);
return this;
}
其对应的内部方法都加了synchronized关键字,所以是线程安全的数据结构。
@Override
public synchronized String toString() {
if (toStringCache == null) {
toStringCache = Arrays.copyOfRange(value, 0, count);
}
return new String(toStringCache, 0, count);
}
如果StringBuffer对象此时存在toStringCache,在多次调用其toString方法时,其new出来的String对象是会共享同一个char[] 内存的,达到共享的目的。但是StringBuffer只要做了修改,其toStringCache属性值都会置null处理。这也是StringBuffer和StringBuilder的一个区别点。
总结
String 类不可变,内部维护的char[] 数组长度不可变,为final修饰,String类也是final修饰,不存在扩容。字符串拼接,截取,都会生成一个新的对象。频繁操作字符串效率低下,因为每次都会生成新的对象。
StringBuilder 类内部维护可变长度char[] , 初始化数组容量为16,存在扩容, 其append拼接字符串方法内部调用System的native方法,进行数组的拷贝,不会重新生成新的StringBuilder对象。非线程安全的字符串操作类, 其每次调用 toString方法而重新生成的String对象,不会共享StringBuilder对象内部的char[],会进行一次char[]的copy操作。
StringBuffer 类内部维护可变长度char[], 基本上与StringBuilder一致,但其为线程安全的字符串操作类,大部分方法都采用了Synchronized关键字修改,以此来实现在多线程下的操作字符串的安全性。其toString方法而重新生成的String对象,会共享StringBuffer对象中的toStringCache属性(char[]),但是每次的StringBuffer对象修改,都会置null该属性值。