简介
总所周知,StringBuffer 是线程安全的,是 JDK 1.0 加入的;StringBuilder 是线程不安全的,是 JDK 1.5 加入的。
String & StringBuffer & StringBuilder 类的定义
public final class String
implements java.io.Serializable, Comparable, CharSequence {
public final class StringBuffer
extends AbstractStringBuilder
implements java.io.Serializable, CharSequence
{
public final class StringBuilder
extends AbstractStringBuilder
implements java.io.Serializable, CharSequence
{
①、三者都实现了 CharSequence 接口。也就是说,CharSequence 定义了字符串操作的接口,三者完成具体的实现;
②、Serializable 是可以序列化的标志;
③、后两者继承了 AbstractStringBuilder 类,而这个类封装了 StringBuilder 和
StringBuffer 大部分操作的实现。
AbstractStringBuilder
AbstractStringBuilder 是 StringBuffer & StringBuilder 两者的父类。
相同点:两者的构造函数都是调用了其父类的构造函数;
不同点:StringBuffer 大部分重写了其父类的方法,并加同步锁(synchronized),速度较慢;而 StringBuilder 则是直接调用其父类的方法,速度更快。
abstract class AbstractStringBuilder implements Appendable, CharSequence {
char[] value;
int count; // value 数组中实际上存放的字符数目
AbstractStringBuilder() {}
AbstractStringBuilder(int capacity) {
value = new char[capacity];
}
public int length() {
return count; //返回的是实际存放的字符数目
}
public int capacity() {
return value.length; //返回的是内置字符数组的长度
}
public void ensureCapacity(int minimumCapacity) {
if (minimumCapacity > 0)
ensureCapacityInternal(minimumCapacity);
}
private void ensureCapacityInternal(int minimumCapacity) {
// 如果需要扩展到的容量 > 当前字符数组长度,那么就正常扩容
if (minimumCapacity - value.length > 0)
expandCapacity(minimumCapacity);
}
//用于保证字符数组长度的方法,ArrayList 中也使用这种动态扩容的思想
void expandCapacity(int minimumCapacity) {
// 初始化新的容量大小 = 当前字符串长度的 2 倍加 2
int newCapacity = value.length * 2 + 2;
// 新容量大小 < 传进来的最小容量,就用最小的容量作为新数组的容量
if (newCapacity - minimumCapacity < 0)
newCapacity = minimumCapacity;
// 如果新的容量和最小容量都小于0
if (newCapacity < 0) {
if (minimumCapacity < 0) // overflow
throw new OutOfMemoryError();
// 把容量设为Integer.MAX_VALUE
newCapacity = Integer.MAX_VALUE;
}
// 创建容量大小为 newCapacity 的新数组
value = Arrays.copyOf(value, newCapacity);
}
// 去除 value 字符数组中所有为空的元素,使其 count = value.length
public void trimToSize() {
if (count < value.length) {
value = Arrays.copyOf(value, count);
}
}
...
}
属性
由 AbstractStringBuilder 类可知,StringBuffer & StringBuilder 父类中封装的字符数组没有 final 修饰,也就说明了 StringBuffer & StringBuilder 中的字符数组可以被不断修改。而 String 则相反。
方法
由于 StringBuffer & StringBuilder 大部分的方法都是直接调用或者重写父类的方法,所以下面我们着重分析其父类 AbstractStringBuilder 的方法
构造方法
StringBuffer & StringBuilder 两者的构造方法是一样的,都需要调用父类的构造方法。存储的字符数组,也是在父类中定义的。
public StringBuilder() {
super(16); // 默认的容量的大小是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);
}
append()
public AbstractStringBuilder append(String str) {
// 如果传入的参数为 null,则直接调用 appendNull() 方法在后面追加 'n'、'u'、'l'、'l' 四个字符。
if (str == null)
return appendNull();
int len = str.length();
// 首先,调用动态扩容方法
ensureCapacityInternal(count + len);
// 将 str 从 0 到 len-1 位置上的字符复制到字符数组 value 中,并从 count
处开始存放(将 str 追加到 value 末尾)
str.getChars(0, len, value, count);
count += len;
// 返回对象本身,使其 append() 可以连续调用
return this;
}
delete()
删除 [start,end) 区域的字符(包括 start,不包括 end)
public AbstractStringBuilder delete(int start, int end) {
// 健壮性的检查
if (start < 0)
throw new StringIndexOutOfBoundsException(start);
if (end > count)
end = count;
if (start > end)
throw new StringIndexOutOfBoundsException();
// 需要删除的长度
int len = end - start;
if (len > 0) {
// 核心语句:将下标 [start,end] 区域的位置,用 [end,count] 区域的字符进行覆盖。
System.arraycopy(value, start+len, value, start, count-end);
// 并更新 count,这样就只能输出[0,count] 区域的字符。
count -= len;
}
return this;
}
delete 的底层操作,并没有真正的删除字符,而是把后面的字符进行前移,从而覆盖。其中 deleteCharAt 也是一样的原理。
insert()
在 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);
// 将 [offset,count] 区域字符串向后移动 len 个位置,为插入字符串留出空间
System.arraycopy(value, offset, value, offset + len, count - offset);
// 将 str 复制到 value 字符数组中 offset 之后的位置
str.getChars(value, offset);
// 更新当前对象中记录的长度
count += len;
return this;
}
该 insert 方法有很多重载,但是本质上都离不开我们上述介绍的这个方法。
replace()
将 [start,end) 区域的字符串替换成 str
public AbstractStringBuilder replace(int start, int end, String str) {
// 健壮性的检查
if (start < 0)
throw new StringIndexOutOfBoundsException(start);
if (start > count)
throw new StringIndexOutOfBoundsException("start > length()");
if (start > end)
throw new StringIndexOutOfBoundsException("start > end");
if (end > count)
end = count;
// 获取需要添加的字符串的长度
int len = str.length();
// 计算新字符串的长度
int newCount = count + len - (end - start);
// 调用扩容方法
ensureCapacityInternal(newCount);
// 将从 end 开始到最后的字符,向后移动到 start+len 的位置
System.arraycopy(value, end, value, start + len, count - end);
// 将 str 复制到 value 字符数组中 start 后面
str.getChars(value, start);
// 更新字符串长度
count = newCount;
return this;
}
toString()
返回了一个新的 String 对象,与原来的对象不共享内存
public String toString() {
return new String(value, 0, count);
}
扩展
①、在编译阶段就能够确定的字符串常量,完全没有必要创建String或StringBuffer对象。直接使用字符串常量的"+"连接操作效率最高;
②、StringBuffer & StringBuilder 对象的 append 效率要高于String 对象的"+"连接操作;
③、在不考虑线程安全的情况下,首选 StringBuilder 类,效率最高。否则选择 StringBuffer。