String、StringBuilder 、StringBuffer在面试中被问到的几率是非常多的。我们常常这样回答:
1.String是不可变的对象
2.StringBuffer是线程安全的,StringBuilder 不是线程安全的
3.StringBuilder 效率高于StringBuffer
如果面试官继续问StringBuffer、StringBuilder 它们底层是怎么实现字符串拼接的,为什么说StringBuffer是线程安全的?如果你没有了解它们底层源码肯定是回答不上。因此这篇文章我们来看看源码。
public final class StringBuilder
extends AbstractStringBuilder
implements java.io.Serializable, CharSequence
{
static final long serialVersionUID = 4383685877147921099L;
//调用父类的构造函数,创建一个长度为16的字符数组
public StringBuilder() {
super(16);
}
//调用父类的构造函数,创建一个指定长度的字符数组
public StringBuilder(int capacity) {
super(capacity);
}
//调用父类的构造函数,传入初始值。构建一个16+初始值长度的数组
public StringBuilder(String str) {
super(str.length() + 16);
append(str);
}
public StringBuilder(CharSequence seq) {
this(seq.length() + 16);
append(seq);
}
@Override
public StringBuilder append(Object obj) {
return append(String.valueOf(obj));
}
@Override
public StringBuilder append(String str) {
super.append(str);
return this;
}
public StringBuilder append(StringBuffer sb) {
super.append(sb);
return this;
}
@Override
public StringBuilder append(CharSequence s) {
super.append(s);
return this;
}
/**
* @throws IndexOutOfBoundsException {@inheritDoc}
*/
@Override
public StringBuilder append(CharSequence s, int start, int end) {
super.append(s, start, end);
return this;
}
@Override
public StringBuilder append(char[] str) {
super.append(str);
return this;
}
/**
* @throws IndexOutOfBoundsException {@inheritDoc}
*/
@Override
public StringBuilder append(char[] str, int offset, int len) {
super.append(str, offset, len);
return this;
}
@Override
public StringBuilder append(boolean b) {
super.append(b);
return this;
}
@Override
public StringBuilder append(char c) {
super.append(c);
return this;
}
@Override
public StringBuilder append(int i) {
super.append(i);
return this;
}
@Override
public StringBuilder append(long lng) {
super.append(lng);
return this;
}
@Override
public StringBuilder append(float f) {
super.append(f);
return this;
}
@Override
public StringBuilder append(double d) {
super.append(d);
return this;
}
}
1.首先你可以看到StringBuilder是final类,是不可被继承的,同时它继承AbstractStringBuilder
2.它的构造函数中都是调用父类AbstractStringBuilder的构造函数。
3.提供了很多append()方法给我们,都是调用父类的append()。
abstract class AbstractStringBuilder implements Appendable, CharSequence {
//AbstractStringBuilder 内部维护着一个char数组
char[] value;
//count值记录数组的元素个数
int count;
AbstractStringBuilder() {
}
//构造函数初始化value数组
AbstractStringBuilder(int capacity) {
value = new char[capacity];
}
//length()方法返回的是数组的元素个数(不是value的长度)
@Override
public int length() {
return count;
}
从上面你可以看到AbstractStringBuilder 主要是维护着一个数组,构造函数初始化value数组,子类传过来的长度就是value的长度。
public AbstractStringBuilder append(String str) {
if (str == null)
return appendNull();
//获取字符串长度
int len = str.length();
//判断是否需要扩容
ensureCapacityInternal(count + len);
//把字符串复制到value数组
//getChars()四个参数:字符串中要复制的第一个字符的索引、字符串中要复制的最后一个字符之后的索引、目标数组、目标数组中的起始偏移量
str.getChars(0, len, value, count);
//记录数组的元素个数
count += len;
return this;
}
private void ensureCapacityInternal(int minimumCapacity) {
//minimumCapacity=元素个数+添加的字符串长度,判断数组容量是否足够
if (minimumCapacity - value.length > 0)
//容量不够则进行扩容
expandCapacity(minimumCapacity);
}
void expandCapacity(int minimumCapacity) {
//计算扩容后的长度(每次扩容是原来的长度俩倍+2)
int newCapacity = value.length * 2 + 2;
//如果扩容后还是不够,直接使用minimumCapacity值作为扩容后的长度
if (newCapacity - minimumCapacity < 0)
newCapacity = minimumCapacity;
if (newCapacity < 0) {
if (minimumCapacity < 0) // overflow
throw new OutOfMemoryError();
newCapacity = Integer.MAX_VALUE;
}
//在value基础上创建一个长度为newCapacity的数组(即在原有数组上进行扩容)
value = Arrays.copyOf(value, newCapacity);
}
append()方法内部主要调用ensureCapacityInternal()是否需要扩容,是的话就把value扩大到原来的2倍+2。最后调用 str.getChars(0, len, value, count)将字符串复制到value数组。
public AbstractStringBuilder delete(int start, int end) {
//start 小于0 抛出异常
if (start < 0)
throw new StringIndexOutOfBoundsException(start);
//start 大于count 抛出异常
if (end > count)
//结束索引默认最大只能为元素个数
end = count;
//起始索引大于结束索引也抛异常
if (start > end)
throw new StringIndexOutOfBoundsException();
int len = end - start;
if (len > 0) {
// arraycopy()可以将源数组复制到目标数组
// 参数1. 原数组(要拷贝的数组)
// 参数2. 要复制的源数组的起始位置下标
// 参数3.目标数组
// 参数4.复制到目标数组的起始位置下标
// 参数5.复制长度
System.arraycopy(value, start+len, value, start, count-end);
//修改元素个数
count -= len;
}
return this;
}
delete()方法首先是对传入的参数进行各种判断:
1.开始索引不能小于0,也不能超过count,且不能大于结束索引
2.结束索引可以随便传,但是最大只能是count
最后就是调用 System.arraycopy()进行删除操作,详细用法可以参考我的博客
java开发:集合(一):数组扩容和元素删除
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();
//重新计算数组需要的长度(因为str长度可能大于要替换的长度)
int newCount = count + len - (end - start);
//判断是否需要扩容
ensureCapacityInternal(newCount);
//移动数组的元素留出空间
System.arraycopy(value, end, value, start + len, count - end);
//把str复制到value数组
str.getChars(value, start);
count = newCount;
return this;
}
replace()同样是对参数进行各种判断,ensureCapacityInternal()判断是否需要扩容。最后先调整数组,再把字符串复制到value数组完成替换。
public final class StringBuffer
extends AbstractStringBuilder
implements java.io.Serializable, CharSequence
{
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);
}
public StringBuffer(CharSequence seq) {
this(seq.length() + 16);
append(seq);
}
StringBuffer同样也是继承AbstractStringBuilder 类
@Override
public synchronized StringBuffer append(Object obj) {
toStringCache = null;
super.append(String.valueOf(obj));
return this;
}
@Override
public synchronized StringBuffer append(String str) {
toStringCache = null;
super.append(str);
return this;
}
public synchronized StringBuffer append(StringBuffer sb) {
toStringCache = null;
super.append(sb);
return this;
}
@Override
synchronized StringBuffer append(AbstractStringBuilder asb) {
toStringCache = null;
super.append(asb);
return this;
}
@Override
public synchronized StringBuffer replace(int start, int end, String str) {
toStringCache = null;
super.replace(start, end, str);
return this;
}
@Override
public synchronized StringBuffer delete(int start, int end) {
toStringCache = null;
super.delete(start, end);
return this;
}
@Override
public synchronized StringBuffer deleteCharAt(int index) {
toStringCache = null;
super.deleteCharAt(index);
return this;
}
StringBuffer的相应api都是使用了synchronized 关键字实现同步锁,因此StringBuffer是线程安全的