分析完StringBuilder,然后再聊StringBuffer就简单多了。因为StringBuffer同样也是继承了AbstractStringBuilder。
1.类结构及成员变量
1.1 类结构
image.png此时对为啥要将StringBuilder还拆分为一个抽象类AbstractStringBuilder恍然大悟了。因为StringBuffer可以复用。
/**
* A thread-safe, mutable sequence of characters.
* A string buffer is like a {@link String}, but can be modified. At any
* point in time it contains some particular sequence of characters, but
* the length and content of the sequence can be changed through certain
* method calls.
* <p>
* 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.
* <p>
* The principal operations on a {@code StringBuffer} are the
* {@code append} and {@code insert} methods, which are
* overloaded so as to accept data of any type. Each effectively
* converts a given datum to a string and then appends or inserts the
* characters of that string to the string buffer. The
* {@code append} method always adds these characters at the end
* of the buffer; the {@code insert} method adds the characters at
* a specified point.
* <p>
* For example, if {@code z} refers to a string buffer object
* whose current contents are {@code "start"}, then
* the method call {@code z.append("le")} would cause the string
* buffer to contain {@code "startle"}, whereas
* {@code z.insert(4, "le")} would alter the string buffer to
* contain {@code "starlet"}.
* <p>
* In general, if sb refers to an instance of a {@code StringBuffer},
* then {@code sb.append(x)} has the same effect as
* {@code sb.insert(sb.length(), x)}.
* <p>
* 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.
* <p>
* Every string buffer has a capacity. As long as the length of the
* character sequence contained in the string buffer does not exceed
* the capacity, it is not necessary to allocate a new internal
* buffer array. If the internal buffer overflows, it is
* automatically made larger.
* <p>
* Unless otherwise noted, passing a {@code null} argument to a constructor
* or method in this class will cause a {@link NullPointerException} to be
* thrown.
* <p>
* As of release JDK 5, this class has been supplemented with an equivalent
* class designed for use by a single thread, {@link StringBuilder}. The
* {@code StringBuilder} class should generally be used in preference to
* this one, as it supports all of the same operations but it is faster, as
* it performs no synchronization.
*
* @author Arthur van Hoff
* @see java.lang.StringBuilder
* @see java.lang.String
* @since JDK1.0
*/
public final class StringBuffer
extends AbstractStringBuilder
implements java.io.Serializable, CharSequence
{
}
其注释大意为:提供一个线程安全,可变的字符串序列。这个字符串序列可以通过一些方法进行调整。
StringBuffer对于对现场而言是安全的,因为内部所有的方法都是同步方法,通过synchronized确保了这些方法的顺序性。主要的一些操作是append和insert等方法。每当涉及到对StringBuffer源序列进行同步操作的时候,这个类仅仅执行字符缓冲区,而不是源对象。
每个StringBuffer有一个容量,只要长度不超过buffer的容量,没必要重新分配。一旦长度大于内部buffer长度,则会自动放大。
将null传递给构造函数会抛出NullPointerException。
StringBuilder与之等效,但是其效率更优,因为它不同步。
1.2 成员变量
与StringBuilder很大的不同是,在StringBuffer中,存在一个toStringCache属性。
/**
* A cache of the last value returned by toString. Cleared
* whenever the StringBuffer is modified.
*/
private transient char[] toStringCache;
/** use serialVersionUID from JDK 1.0.2 for interoperability */
static final long serialVersionUID = 3388685877147921107L;
这个成员变量的意思是说在toString需要的时候,将最新的value中的内容。如果有修改的时候则将这个cache清空。
2.toString方法比较
我们在前面知道,StringBuffer比StringBuilder多了一个toStringCache数组,为什么StringBuffer的实现与StringBuilder不同呢?我们对其代码进行分析。
2.1 StringBuilder.toString
@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) {
if (offset < 0) {
throw new StringIndexOutOfBoundsException(offset);
}
if (count <= 0) {
if (count < 0) {
throw new StringIndexOutOfBoundsException(count);
}
if (offset <= value.length) {
this.value = "".value;
return;
}
}
// Note: offset or count might be near -1>>>1.
if (offset > value.length - count) {
throw new StringIndexOutOfBoundsException(offset + count);
}
this.value = Arrays.copyOfRange(value, offset, offset+count);
}
实际上这个还是使用的是 Arrays.copyOfRange。其底层是通过System.arraycopy进行的对象拷贝。
2.2 StringBuffer.toString
@Override
public synchronized String toString() {
if (toStringCache == null) {
toStringCache = Arrays.copyOfRange(value, 0, count);
}
return new String(toStringCache, true);
}
在看看String的这个构造方法:
/*
* Package private constructor which shares value array for speed.
* this constructor is always expected to be called with share==true.
* a separate constructor is needed because we already have a public
* String(char[]) constructor that makes a copy of the given char[].
*/
String(char[] value, boolean share) {
// assert share : "unshared not supported";
this.value = value;
}
这个构造方法只提供了同package中的类才能访问到。
我们可以看到,实际上是一开始,在String类中定义了一个char数组,之后调用这个String比较特殊的构造函数,直接修改指针,指向这个共享的数组。
最开始我也没看明白,这个toStringCache的意义。
但是,我们试想一种情况,在多线程的情况下,如果使用了StringBuffer,且其值不发生改变的时候,就可以直接复用前一次的结果。虽然多个线程new了不同的String,但是其本质的char数组只有一个,不会重复的System.arraycopy。因此会带来性能的提升。虽然StringBuffer的toString方法本身是synchronized的,但是这样一来可以降低对象拷贝带来的性能损耗。
3.同步
StringBuffer与StringBuilder最大的区别在于同步,可以简单认为StringBuffer就是加上了synchronized的StringBuilder。实际上StringBuffer的实现也是在各个方法上加上synchronized关键字。
@Override
public synchronized int length() {
return count;
}
@Override
public synchronized int capacity() {
return value.length;
}
@Override
public synchronized void ensureCapacity(int minimumCapacity) {
super.ensureCapacity(minimumCapacity);
}
/**
* @since 1.5
*/
@Override
public synchronized void trimToSize() {
super.trimToSize();
}
/**
* @throws IndexOutOfBoundsException {@inheritDoc}
* @see #length()
*/
@Override
public synchronized void setLength(int newLength) {
toStringCache = null;
super.setLength(newLength);
}
public synchronized StringBuffer append(StringBuffer sb) {
toStringCache = null;
super.append(sb);
return this;
}
/**
* @since 1.8
*/
@Override
synchronized StringBuffer append(AbstractStringBuilder asb) {
toStringCache = null;
super.append(asb);
return this;
}
可以看到所有核心的方法都是加上synchronized之后调用的super方法。
在对String中char数组有结构性变更的地方都会将toStringCache变为null。
4.总结
StringBuffer与StringBuilder的主要区别在于:
- StringBuffer是线程安全的,适合在多线程环境下使用,StringBuilder不是线程安全的,只能在单线程下使用。
- 二者的toString方法实现不同,StringBuffer中考虑到了多线程环境,因而进行了优化,加入了toStringCache,当StringBuffer固定不变时多线程进行范围的时候可以有效复用。不会反复调用System.arraycopy。但是这个优化对于StringBuilder实际上意义不大,StringBuilder本身不具有线程安全性。尤其是对于toString方法,一定不要混淆其使用场景。可以参考一次简单却致命的错误。该文章说明了一次由于错误使用toString方法而带来的问题。
网友评论