在实际的项目开发过程中,可能我们更多时候是在操作可变的字符串,例如,动态的拼接SQL语句。
之所以叫StringBuilder和StringBuffer为可变字符串是因为:
(1)提供了大量改变原字符串的操作,如append()方法。而改变是在原来字符串的基础上修改的,并不像String中的一些方法,通过new String()来返回一个新的字符串。
(2)字符串会自动扩容,不需要人为的进行操作,而String本身是不进行扩容的。
1、两者的类头:
public final class StringBuffer
extends AbstractStringBuilder
implements java.io.Serializable, CharSequence
public final class StringBuilder
extends AbstractStringBuilder
implements java.io.Serializable, CharSequence
首先看接口为相同的:java.io.Serializable 和 CharSequence
说明两者都可被序列化 和 需要注意:CharSequence就是字符序列,String, StringBuilder和StringBuffer本质上都是通过字符数组实现的!
2、源码方法
再看父类,都是 AbstractStringBuilder,在看下AbstractStringBuilder的,Appendable是纯接口里面只是几中append抽象方法,没有实现。AbstractStringBuilder它的实现类中的重写方法都是调用super.xxx(),所以只用看它源码就行。
(1)、append(String str)
abstract class AbstractStringBuilder implements Appendable, CharSequence {
char[] value;
int count; //长度
AbstractStringBuilder() {} //无参构造
AbstractStringBuilder(int capacity) { //初始化一个长度为capacity的char[]
value = new char[capacity];
}
//下面是append的最主要的
public AbstractStringBuilder append(String str) {
//填一个空的
if (str == null) return appendNull();
//添加的字符串的长度
int len = str.length();
//进行扩容,就是原来的增加2倍+2(不绝对),最后面会有他们的源码解析
ensureCapacityInternal(count + len);
//从0到len的str全部长度拷贝到value[]从count位置开始,也就是从结尾开始拷贝str的全部进行追加
str.getChars(0, len, value, count);
count += len;
return this;
}
}
有许多的append()方法,处理方法都非常类似,有兴趣的可以自己去看。append()方法只能在末尾进行追加,如果要对字符串中间进行插入,则可以使用insert()方法,它也有很多方法,举个例子:
(2)、insert(int offset,String str)
//offset要拷贝的开始位置就是原本的结尾位置,str为追加的串
public AbstractStringBuilder insert(int offset, String str) {
if ((offset < 0) || (offset > length()))
throw new StringIndexOutOfBoundsException(offset);
if (str == null)
str = "null";
int len = str.length();
ensureCapacityInternal(count + len);//扩容
System.arraycopy(value, offset, value, offset + len, count - offset);// 从value中的指定位置向后腾出len个位置
str.getChars(value, offset); // 将str中包含的字符插入value的指定位置
count += len;
return this;
}
可以看到,都会进行自动扩容,然后将str字符串中的字符插入到value中的指定位置。由于会自动扩容,所以在使用可变的字符串时,最好指定足够的容量,以防止进行扩容。因为底层需要进行数组的拷贝。
测试程序如下:
StringBuilder str =new StringBuilder("abc");
str.insert(1, "xx");
System.out.println(str.toString());// axxbc
3、同步问题:
StringBuilder和StringBuffer类都实现了这个抽象类,只是StringBuffer类中公开的方法都加上了synchronized关键字进行同步,也是与StringBuilder的最大不同。
例如:StringBuffer,它的每个方法都加了synchronized,保证线程安全
@Override
public synchronized StringBuffer append(String str) {
toStringCache = null;
super.append(str);
return this;
}
但是对于StringBuffer的序列化与反序列化时,只有writeObject加了synchronized,而readObject跟StringBuilder的readObject一样没有加synchronized。
private synchronized void writeObject(java.io.ObjectOutputStream s)
throws java.io.IOException {
java.io.ObjectOutputStream.PutField fields = s.putFields();
fields.put("value", value);
fields.put("count", count);
fields.put("shared", false);
s.writeFields();
}
private void readObject(java.io.ObjectInputStream s)
throws java.io.IOException, ClassNotFoundException {
java.io.ObjectInputStream.GetField fields = s.readFields();
value = (char[])fields.get("value", null);
count = fields.get("count", 0);
}
CharSequence是个接口,表示字符序列,而String、StringBuilder和StringBuffer都实现这个接口,底层都是通过数组来实现的。
4、String、StringBuilder、StringBuffer三个类进行比较:
异同点:
- String的对象是不可变的;而StringBuilder和StringBuffer是可变的。
- StringBuilder不是线程安全的;而StringBuffer是线程安全的
- String中的offset,value,count都是被final修饰的不可修改的;而StringBuffer和StringBuilder中的value,count都是继承自AbstractStringBuilder类的,没有被final修饰,说明他们在运行期间是可修改的,而且没有offset变量。
相同点:
三个类都是被final修饰的,是不可被继承的。且都能表示表示字符序列(源代码中可以看出都继承了CharSequence.java类 )
5、其他源码:
(1)、getChars()源码:
实际是它里面的System.arraycopy达到了进行追加的功能
////从srcBegin到srcEnd的str全部长度拷贝到dst[]从dstBegin位置开始,也就是从结尾开始拷贝str的全部进行追加
public void getChars(int srcBegin, int srcEnd, char[] dst, int dstBegin)
{
if (srcBegin < 0)
throw new StringIndexOutOfBoundsException(srcBegin);
if ((srcEnd < 0) || (srcEnd > count))
throw new StringIndexOutOfBoundsException(srcEnd);
if (srcBegin > srcEnd)
throw new StringIndexOutOfBoundsException("srcBegin > srcEnd");
//下面这个方法是native方法,实现数组之间的复制,value表示源数组,srcBegin表示源数组要复制的起始位置,dst表示目标数组,dstBegin为目标数组其实位置,length表示从目标复制到源数组的长度。
System.arraycopy(value, srcBegin, dst, dstBegin, srcEnd - srcBegin);
}
(2)、拷贝System.arraycopy的函数原型是(native方法):
public native static void arraycopy(Object src,
int srcPos,
Object dest,
int destPos,
int length)
其中:src表示源数组,srcPos表示源数组要复制的起始位置,desc表示目标数组,length表示要复制的长度。
(3)、扩容ensureCapacityInternal()源码:
Integer.MAX_VALUE为啥要减8,数组对象的形状和结构(如int值数组)与标准Java对象类似。主要区别在于数组对象有一个额外的元数据,用于表示数组的大小。这个8就是元数据大小。
//Integer.MAX_VALUE是int的最大值(2^31),减8是减去存储数组的大小。
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
//扩容的函数,就是原来的value不变,只是将长度增加,原本字符串的长度增加为原来的2倍+2
private void ensureCapacityInternal(int minimumCapacity) {
// overflow-conscious code
if (minimumCapacity - value.length > 0) {
//第一个参数是,源数组,第二个参数是要拷贝到源数组的长度
value = Arrays.copyOf(value,newCapacity(minimumCapacity));
}
}
private int newCapacity(int minCapacity) {
// 原本字符串的长度安位左移1位,相当于*2
int newCapacity = (value.length << 1) + 2;
//如果加进来的容量比原本字符串的2倍+2还要大的话,就变为大的。不过一般都是minCapacity=value.length+str.length,
//所以如果str.length的长度要是大于value.length+2的话,就不会按照原本字符串+2进行长度的分配,而是按这个minCapacity分配。
if (newCapacity - minCapacity < 0) {
newCapacity = minCapacity;
}
return (newCapacity <= 0 || MAX_ARRAY_SIZE - newCapacity < 0)
? hugeCapacity(minCapacity)
: newCapacity;
}
//获取比MAX_ARRAY_SIZE大于等于的值,小于Integer.MAX_VALUE的值,只要不越界
private int hugeCapacity(int minCapacity) {
//Integer.MAX_VALUE是没减8的也就是int的最大值,如果他俩相减还小于0,说明越界
if (Integer.MAX_VALUE - minCapacity < 0) { overflow
throw new OutOfMemoryError();
}
//除此之外,如果MAX_ARRAY_SIZE < minCapacity < Integer.MAX_VALUE就取它本身,要是小于MAX_ARRAY_SIZE 那就取MAX_ARRAY_SIZE
return (minCapacity > MAX_ARRAY_SIZE)
? minCapacity : MAX_ARRAY_SIZE;
}