在讨论StringBuilder和StringBuffer之前,我们有必要先说一下AbstractStringBuilder。
StringBuilder和StringBuffer都继承自AbstractStringBuilder,AbstractStringBuilder也定义了大量的方法和变量。
A mutable sequence of characters.
Implements a modifiable string.另外可以观察到这个类是一个抽象类,不可以实例化,实现了Appendable和CharSequence接口。
Appendable中重载了好几个append方法,
Appendable append(CharSequence csq) throws IOException;
Appendable append(CharSequence csq, int start, int end) throws IOException;
Appendable append(char c) throws IOException;
虽然没有直接定义和参数和String相关的方法,但正是因为String也继承了CharSequence,所以可以传入String,甚至可以传入StringBuffer和StringBuilder。CharSequence我们很熟悉了,String、StringBuilder和StringBuffer都实现了这个接口。
/**
* This no-arg constructor is necessary for serialization of subclasses.
*/
AbstractStringBuilder() {
}
子类如果能够序列化的话,其父类需要有无参的构造函数。
/**
* Creates an AbstractStringBuilder of the specified capacity.
*/
AbstractStringBuilder(int capacity) {
value = new char[capacity];
}
在构造函数中决定缓冲区的空间大小。即决定初始空间的大小。
char[] value;
The value is used for character storage.
和String一样,也使用char数组存储实际的字符信息。public int capacity() {
return value.length;
}
获得capacity,即容量。int count;
The count is the number of characters used.
StringBuilder/StringBuffer的工作方式并不是需要多大空间就开多大空间,而是预先开出一定的空间供使用,所以如果没有使用满,自然需要一个变量去记录已经使用的空间是多大。public int length() {
return count;
}
获得使用量,也就是实际长度,这个方法是定义在CharsSequence中的。public void ensureCapacity(int minimumCapacity) {
......
}
这里省略方法体和里面的复杂调用。
* Ensures that the capacity is at least equal to the specified minimum.
* If the current capacity is less than the argument, then a new internal
* array is allocated with greater capacity. The new capacity is the
* larger of:
*
*- The {@code minimumCapacity} argument.
*- Twice the old capacity, plus {@code 2}.
这个方法的功能是当当前容量小于minimumCapacity,且minimumCapacity合法的话,修改容量大小。否则没有变化。
注意,并不是直接把容量修改为minimumCapacity的值。如果当前容量的两倍小于minimumCapacity,则开到minimumCapacity大小的空间,否则把容量扩充为当前的两倍。
换言之,就是最少扩大为原来的两倍。public void trimToSize() {
if (count < value.length) {
value = Arrays.copyOf(value, count);
}
}
缩小容量到实际大小,是开了一个新数组的,丢弃的数组会被GC回收。public void setLength(int newLength) {
if (newLength < 0)
throw new StringIndexOutOfBoundsException(newLength);
ensureCapacityInternal(newLength);if (count < newLength) {
Arrays.fill(value, count, newLength, '\0');
}count = newLength;
}
修改数组的实际长度,如果空间不够会首先开足够空间,新增空间的规则和上面讲得一样。新增的长度用\0填充。
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;
}// Documentation in subclasses because of synchro difference
public AbstractStringBuilder append(StringBuffer sb) {
if (sb == null)
return appendNull();
int len = sb.length();
ensureCapacityInternal(count + len);
sb.getChars(0, len, value, count);
count += len;
return this;
}
这里可以看到最常用的append方法是怎么实现的,先确保空间足够,再把新串复制过去。
public AbstractStringBuilder deleteCharAt(int index) {
if ((index < 0) || (index >= count))
throw new StringIndexOutOfBoundsException(index);
System.arraycopy(value, index+1, value, index, count-index-1);
count--;
return this;
}
删除指定位置的字符, 可以看到是把index后面的整体向前移动一个位置。
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);
str.getChars(value, offset);
count += len;
return this;
}
可以看到插入的原理,先判断空间是否足够, 然后将插入位置后面的字符后移,再把欲插入字符复制进去。
public AbstractStringBuilder reverse() {
boolean hasSurrogates = false;
int n = count - 1;
for (int j = (n-1) >> 1; j >= 0; j--) {
int k = n - j;
char cj = value[j];
char ck = value[k];
value[j] = ck;
value[k] = cj;
if (Character.isSurrogate(cj) ||
Character.isSurrogate(ck)) {
hasSurrogates = true;
}
}
if (hasSurrogates) {
reverseAllValidSurrogatePairs();
}
return this;
}
String中并没有定义字符串反转相关的方法,所以想要实现反转需要关注这个方法。
这个方法可以说是非常精巧了,j为中心靠低下标的字符,k为中心靠高下标的字符。像两边扩展进行交换。
A mutable sequence of characters.
开宗明义,和String最大的区别。This class provides an API compatible with {@code StringBuffer}, but with no guarantee of synchronization.
功能上和StringBuffer兼容,只是线程不安全。
说明这两个类非常常用,为了性能的考虑可以自由选择。* This class is designed for use as a drop-in replacement for
* {@code StringBuffer} in places where the string buffer was being
* used by a single thread (as is generally the case). Where possible,
* it is recommended that this class be used in preference to
* {@code StringBuffer} as it will be faster under most implementations.
后面也可也可以看到,这两个类其实是实现了同一个AbstractStringBuilder接口的,所以设计者希望这两个类可以自由地互相替换。* In general, if sb refers to an instance of a {@code StringBuilder},
* then {@code sb.append(x)} has the same effect as
* {@code sb.insert(sb.length(), x)}.* Every string builder has a capacity. As long as the length of the
* character sequence contained in the string builder does not exceed
* the capacity, it is not necessary to allocate a new internal
* buffer. If the internal buffer overflows, it is automatically made larger.
每一个builder是有容量的。如果超过容量,会自动扩容。*
Unless otherwise noted, passing a {@code null} argument to a constructor
* or method in this class will cause a {@link NullPointerException} to be
* thrown.
构造方法传null会抛出异常。
StringBuilder继承了AbstractStringBuilder,并且实现了java.io.Serializable和CharSequence接口。
StringBuilder和StringBuffer都继承了AbstractStringBuilder这个父类。
CharSequence我们前面讲过,定义了包括length、charAt、subSequence和toString等方法。
Serializable会在后面详细解释。
public StringBuilder() {
super(16);
}public StringBuilder(int capacity) {
super(capacity);
}public StringBuilder(String str) {
super(str.length() + 16);
append(str);
}
可以指定初始化开的容量大小,默认大小是16个字符。
如果是用字符串初始化,也会默认多开16的空间。
@Override
public String toString() {
// Create a copy, don't share the array
return new String(value, 0, count);
}
toString方法特别标明,为了保持String的不变性,并没有共享字符数组的空间。StringBuilder中大量的方法其实就是对AbstractStringBuilder的薄封装,没有太多看点。
* String buffers are safe for use by multiple threads. The methods
* are synchronized where necessary so that all the operations on any
* particular instance behave as if they occur in some serial order
* that is consistent with the order of the method calls made by each of
* the individual threads involved.
StringBuffer线程安全。* Whenever an operation occurs involving a source sequence (such as
* appending or inserting from a source sequence), this class synchronizes
* only on the string buffer performing the operation, not on the source.
* Note that while {@code StringBuffer} is designed to be safe to use
* concurrently from multiple threads, if the constructor or the
* {@code append} or {@code insert} operation is passed a source sequence
* that is shared across threads, the calling code must ensure
* that the operation has a consistent and unchanging view of the source
* sequence for the duration of the operation.
* This could be satisfied by the caller holding a lock during the
* operation's call, by using an immutable source sequence, or by not
* sharing the source sequence across threads.
大意就是说,虽然能够在操作source sequence这件事情上保持同步,但是在source sequence本身并没有保证同步。这就意味着如果你能通过别的方式引用source sequence,还是会有线程不安全的风险。
解决的方案如下:
1 by using an immutable source sequence
2 or by not sharing the source sequence across threads.
同StringBuilder一样,继承AbstractStringBuilder类,并且实现java.io.Serializable和CharSequence接口。
public StringBuffer() {
super(16);
}
public StringBuffer(int capacity) {
super(capacity);
}
public StringBuffer(String str) {
super(str.length() + 16);
append(str);
}
同StringBuilder的构造方法类似,开得空间也相同。要注意构造方法并没有同步机制,所以在单例模式的时候可能需要使用者来进行实现。
/**
* A cache of the last value returned by toString. Cleared
* whenever the StringBuffer is modified.
*/
private transient char[] toStringCache;
这个变量在StringBuilder中是没有的。
这个方法的作用是用来缓存上一次StringBuffer的toString方法的返回值。
如果哪个方法中修改了StringBuffer,则会把toStringCache给设为null。首先我们需要比较以下StringBuffer和StringBuilder的toString方法:
@Override
public synchronized String toString() {
if (toStringCache == null) {
toStringCache = Arrays.copyOfRange(value, 0, count);
}
return new String(toStringCache, true);
}
上面这个方法中调用了下面这个String的构造方法:
String(char[] value, boolean share) {
// assert share : "unshared not supported";
this.value = value;
}
可以看到这个构造方法只是单纯引用了StringBuffer的值,而并没有新开一个数组,这是很特别的情况。当然如果toStringCache为null的话,也是直接返回了一个全新的String的。@Override
public String toString() {
// Create a copy, don't share the array
return new String(value, 0, count);
}
public String(char value[], int offset, int count) {
......
this.value = Arrays.copyOfRange(value, offset, offset+count);
}
而在StringBuilder方法中,则是直接拷贝产生了一个新的数组。
并且toString中解释道Create a copy, don't share the array。再联系到StringBuffer和StringBuilder的区别我们可以想到:
StringBuilder多用在单线程或者局部作用域中,StringBuffer用得多线程中。
StringBuffer的声明周期可能比较长,并且使用到的地方会比较多,使用这样一个缓存的方式,可以在一定程度减少空间和性能的消耗。
https://stackoverflow.com/questions/46294579/why-stringbuffer-has-a-tostringcache-while-stringbuilder-not另外还要解释一下transient:
Variables may be marked transient to indicate that they are not part of the persistent state of an object.
其实这个变量还是和序列化相关的,因为这个变量只是一个cache,所以不需要保存。
https://stackoverflow.com/questions/910374/why-does-java-have-transient-fields
其实比较StringBuffer和StringBuilder,区别并不是很大,最大的区别就是在StringBuffer中,大多数修改char数组的方法都被synchronized修饰。
并且在append和insert等修改StringBuffer方法中,需要对toStringCache设为null。
不过有特殊情况在于有些insert方法是并没有被synchronized修饰的。例如下面这个方法:
@Override
public StringBuffer insert(int offset, boolean b) {
// Note, synchronization achieved via invocation of StringBuffer insert(int, String)
// after conversion of b to String by super class method
// Ditto for toStringCache clearing
super.insert(offset, b);
return this;
}
注释大概的意思是,这个方法的同步最终是通过StringBuffer insert(int, String)进行实现的。我们大概追踪一下调用过程:
首先这个方法调用了父类的insert方法,
public AbstractStringBuilder insert(int offset, boolean b) {
return insert(offset, String.valueOf(b));
}
而父类中调用的这个方法,其实在StringBuffer中被重写,所以调用的是StringBuffer中重写后并且被synchronized修饰的方法:
@Override
public synchronized StringBuffer insert(int offset, String str) {
toStringCache = null;
super.insert(offset, str);
return this;
}除了boolean,其他的例如float、int、double、long等在插入过程中调用toString方法的类型也是这种情况。但是char不是这种情况,char直接在insert方法上修饰synchronized,因为char在父类中不是使用valueOf进行实现的。