StringBuilder与StringBuffer
引言
Stringbuffer与StringBuider一直是面试中经常考察的一道题目,之前也经常复习但是常常搞混,但是个人经验通过阅读源码的方式来加深对于题目的理解往往会让你对这两个类记忆更加深刻,同时也可以让你在回答面试问题的时候让面试官对你刮目相看
首先看到两个类图的结构
可以看到两个类都实现了Comparable、Serializable接口、AbstractStringBuilder接口
首先看到AbstractStringBuilder
abstract class AbstractStringBuilder implements Appendable, CharSequence {
/**
* The value is used for character storage.
*/
byte[] value;
/**
* The id of the encoding used to encode the bytes in {@code value}.
*/
byte coder;
/**
* The count is the number of characters used.
*/
int count;
我们可以看到实际上两个类都是使用byte数组来存储单个字符的
直接来分析append方法
public AbstractStringBuilder append(String str) {
if (str == null) {
return appendNull();//判断字符串是否为空
}
int len = str.length();
//字符串的长度
ensureCapacityInternal(count + len);
putStringAt(count, str);
count += len;
return this;
}
private void ensureCapacityInternal(int minimumCapacity) {
// overflow-conscious code
int oldCapacity = value.length >> coder;
if (minimumCapacity - oldCapacity > 0) {
value = Arrays.copyOf(value,
newCapacity(minimumCapacity) << coder);
}
}
AbstractStringBuilder(int capacity) {
if (COMPACT_STRINGS) {
value = new byte[capacity];
coder = LATIN1;
} else {
value = StringUTF16.newBytesFor(capacity);
coder = UTF16;
}
}
首先在初始化AbstractStringBuilder的时候,会传入初始容量。对于StringBuilder与StringBuffer来说默认传入的初始容量都为16,COMPACT_STRING的值是由jvm注入的,与字符串压缩相关。
我们可以看到构造函数实际上是初始化一个字节数组。LATIN的值为1而UTF16的值为2,原因就是LATIN指字符可以完全由byte值容纳的编码,UTF16大概就指一个字符完全需要由两个byte组成的这种编码,所以在ensureCapacityInternal方法里,根据所占字符长度规则来判断是否需要扩容。
int newCapacity = (oldCapacity << 1) + 2;
每次扩容长度为2*原长度+2
调用 Arrays.copyOf 将原数组扩容并拷贝到一个新字节数组中
回答的第一个点: 两个类都是实现了AbstractStringBuilder接口,并且底层使用byte数组存储字符
两者区别
- StringBuffer是线程安全的,StringBuilder是线程不安全的
public synchronized int codePointCount(int beginIndex, int endIndex) {
return super.codePointCount(beginIndex, endIndex);
}
/**
* @throws IndexOutOfBoundsException {@inheritDoc}
* @since 1.5
*/
@Override
public synchronized int offsetByCodePoints(int index, int codePointOffset) {
return super.offsetByCodePoints(index, codePointOffset);
}
/**
* @throws IndexOutOfBoundsException {@inheritDoc}
*/
@Override
public synchronized void getChars(int srcBegin, int srcEnd, char[] dst,
int dstBegin)
{
super.getChars(srcBegin, srcEnd, dst, dstBegin);
}
/**
* @throws IndexOutOfBoundsException {@inheritDoc}
* @see #length()
*/
@Override
public synchronized void setCharAt(int index, char ch) {
toStringCache = null;
super.setCharAt(index, ch);
}
@Override
public synchronized StringBuffer append(Object obj) {
toStringCache = null;
super.append(String.valueOf(obj));
return this;
}
我们可以看到StringBuffer的所有公开方法都是采用synchronized关键字来修饰的,这意味着在操作方法的时候必须获取对象锁。虽然在单线程的情况下锁只是轻量级的,但是还是会有获取锁需要的开销,因此在单线程情况下,我们选择采用StringBuilder
- 字符串缓存
public final class StringBuffer
extends AbstractStringBuilder
implements java.io.Serializable, Comparable<StringBuffer>, CharSequence
{
/**
* A cache of the last value returned by toString. Cleared
* whenever the StringBuffer is modified.
*
*/
private transient String toStringCache;
根据英文注释,我们可以知道这是一个每次调用toString方法后的缓存,当每次StringBuffer被修改后清除
public synchronized String toString() {
if (toStringCache == null) {
return toStringCache =
isLatin1() ? StringLatin1.newString(value, 0, count)
: StringUTF16.newString(value, 0, count);
}
return new String(toStringCache);
}
这样在每次调用toString()方法时,不需要每次产生一个新的String对象,而StringBuilder则没有缓存机制
public String toString() {
// Create a copy, don't share the array
return isLatin1() ? StringLatin1.newString(value, 0, count)
: StringUTF16.newString(value, 0, count);
}