先说结论,
- String对象是不可变的,对String对象的增删改,实际上是创建了一个新的对象。
- StringBuffer是可变的,对StringBuffer对象的增删改是不会创建新对象的,并且它是线程安全的。
- StringBuilder是可变的,对StringBuilder对象的增删改是不会创建新对象的,它不是线程安全的。
1.String
结论:String对象是不可变的,对String对象的增删改,实际上是创建了一个新的对象。
上论据,我们直接看一下String类的源码。
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence {
/** The value is used for character storage. */
private final char value[];
...
// 省略其他代码
...
}
可以看到String类中有一个char类型的数组value,官方注释说明了这个数组用来存String字符串。
数组被final关键字修饰了,众所周知,Java语言中,被final修饰的都是不可变的。
拓展
再看看官方注释对String类的描述
/**
* The String class represents character strings. All string literals in Java
* programs, such as "abc", are implemented as instances of this class.
*/
翻译成中文就是
String类表示字符串。Java程序中的所有字符串文本(如“abc”)都作为此类的实例实现。
所以我们如果直接使用"abc","123"等字符串,都是String类型的。
2.StringBuffer
结论:StringBuffer是可变的,对StringBuffer对象的增删改不会创建新对象,且它是线程安全的。
上论据,看源码
public final class StringBuffer
extends AbstractStringBuilder
implements java.io.Serializable, CharSequence
{
// 省略代码
}
StringBuffer本身没有定义字符串的存储结构,但StringBuffer是继承自AbstractStringBuilder这个抽象类的,我们再看看AbstractStringBuilder的源码。
abstract class AbstractStringBuilder implements Appendable, CharSequence {
/**
* The value is used for character storage.
*
* 翻译;用来存储字符
*/
char[] value;
/**
* The count is the number of characters used.
*
* 翻译;用来记录当前已经使用的字符的数量
*/
int count;
// 省略其他代码
}
AbstractStringBuilder类中和String类一样,用一个数组作为字符串的数据结构,但它没有被final修饰,是可以修改的。
至此,我们得到结论的第一部分,StringBuffer是可变的。
接着我们看一下StringBuffer【添加字符串】的方法,即append的方法是怎么实现的,看看操作StringBuffer的时候是否会产生新对象。
@Override
public synchronized StringBuffer append(String str) {
toStringCache = null;
super.append(str);
return this;
}
append方法核心是调用了父类的append方法,即AbstractStringBuilder类的append方法,那我们再去AbstractStringBuilder那里看看。
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;
}
这里通过调用String类的getChars方法来添加字符串。
str.getChars(0, len, value, count);
这几个参数需要注意一下。
str就是append(String str)里被添加的字符串,是String类型的。
value就是AbstractStringBuilder中可变的数组。
count 是AbstractStringBuilder使用来记录当前value数组已经被使用的数量。
继续看String类中的getChars方法是怎么实现的。
public void getChars(int srcBegin, int srcEnd, char dst[], int dstBegin) {
if (srcBegin < 0) {
throw new StringIndexOutOfBoundsException(srcBegin);
}
if (srcEnd > value.length) {
throw new StringIndexOutOfBoundsException(srcEnd);
}
if (srcBegin > srcEnd) {
throw new StringIndexOutOfBoundsException(srcEnd - srcBegin);
}
System.arraycopy(value, srcBegin, dst, dstBegin, srcEnd - srcBegin);
}
这里最核心的代码就是System.arraycopy,这个方法的作用是什么呢?
/**
* Copies an array from the specified source array,
* beginning at the specified position,
* to the specified position of the destination array.
* A subsequence of array components are copied from the source array
* referenced by src to the destination array referenced by dest.
* The number of components copied is equal to the length argument.
* The components at positions srcPos through srcPos+length-1
* in the source array are copied into positions destPos through
* destPos+length-1, respectively, of the destination array.
*
* Params:
* src – the source array.
* srcPos – starting position in the source array.
* dest – the destination array.
* destPos – starting position in the destination data.
* length – the number of array elements to be copied.
*/
public static native void arraycopy(Object src, int srcPos,
Object dest, int destPos,
int length);
这是一个本地方法,关于什么是本地方法,朋友们百度一下很快知道是什么,我就不赘述了,arraycopy的源码注释如上,翻译过来就是
把一个指定的源数组,从指定位置开始,复制到另一个目标数组的指定位置。 把一个源数组的子序列(也就是数组的的一部分)从src引用的源数组复制到dest引用的目标数组。 复制的序列长度数等于长度参数length。 将源数组中位置 srcPos 到 srcPos+length-1的数据序列 复制到目标数组destPos 到 destPos+length-1的位置。 参数: src – 源数组。 srcPos – 源数组中的起始位置。 dest – 目标数组。 destPos – 目标数组中的起始位置。 length – 要复制的数组元素数。
System.arraycopy的作用就是将一个数组某一部分复制到另外一个数组的某个指定位置。
知道这个方法是干什么的,我们再回去看看当我们使用StringBuffer添加一个字符串的时候,传给arraycopy的参数的值分别是什么。
src – 被添加的字符串数组,即append的入参。
srcPos – 源数组中的起始位置,这里是0。
dest – 目标数组,这里是StringBuffer里面的可变数组。
destPos – 目标数组的起始位置,这里使用了count,即当前已经使用的字符的数量,
就是StringBuffer可变数组的最后一个字符的位置 + 1。
length – 要复制的数组元素数,被添加的字符串数组的长度。
从这里我们可以看出,每次StringBuffer调用append方法,都是将一个String类型的字符串追加到了StringBuffer的可变数组中,没有创建新的对象,StringBuffer其他操纵字符串的方法本质也是如此,就不一一列出来,有兴趣可以看源码。
结论第二阶段,StringBuffer是可变的,且操作StringBuffer不会产生新对象。
StringBuffer的每个方法都有一个synchronized关键字进行修饰,加了这个关键字,就说明这个方法加上了synchronized锁,加上了synchronized锁,就一定能避免线程安全问题。
所以得到最终结论,StringBuffer是可变的,操作StringBuffer不会产生新对象,且StringBuffer是线程安全的。
3.StringBuilder
结论:StringBuilder是可变的,对StringBuilder对象的增删改是不会创建新对象的,它不是线程安全的。
上论据:
public final class StringBuilder
extends AbstractStringBuilder
implements java.io.Serializable, CharSequence
{
// 省略代码
}
可以看到,StringBuilder和StringBuffer一样,也是继承自AbstractStringBuilder来实现字符串的增删改的,那么对于是否可变和是否会产生新对象的分析,所有的分析都和StringBuffer是一样的。
不过,StringBuilder的方法都没有使用synchronized锁。
直接看StringBuffer和StringBuilder的append方法
public final class StringBuffer
extends AbstractStringBuilder
implements java.io.Serializable, CharSequence
{
@Override
public synchronized java.lang.StringBuffer append(String str) {
toStringCache = null;
super.append(str);
return this;
}
}
public final class StringBuilder
extends AbstractStringBuilder
implements java.io.Serializable, CharSequence
{
@Override
public java.lang.StringBuilder append(String str) {
super.append(str);
return this;
}
}
所以,StringBuilder是可变的,对StringBuilder对象的增删改是不会创建新对象的,但它不是线程安全的。
适用场景
分析
- StringBuffer加了锁,所以创建StringBuffer对象的性能比StringBuilder差。
- String对象不可变,如果对字符串的改变比较多,就会产生一堆String对象占用内存。
结论
- 如果不需考虑线程安全,且对字符串的修改比较频繁,应该使用StringBuilder对象,使用StringBuffer对象因为加了锁影响创建效率,String对象会占用内存。
- 如果不需考虑线程安全,且修改不频繁,可以直接使用String对象,这时候使用String对象,对内存影响不大,而且对开发者而言,操作比较方便。
- 如果需要考虑线程安全,那就只能使用StringBuffer。