在Java中,使用StringBuffer和StringBuilder这两个类表示可变长字符串,这篇博文用来探讨这两个类的源码分析,所有源码都是开源的,附上版权要求:
/*
* Copyright (c) 1994, 2013, Oracle and/or its affiliates. All rights reserved.
* ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
*/
继承关系
这两个类的继承关系完全一样,都是如下所示:
public final class StringBuilder
extends AbstractStringBuilder
implements java.io.Serializable, CharSequence
public final class StringBuffer
extends AbstractStringBuilder
implements java.io.Serializable, CharSequence
在继承关系中可以看到,都继承了一个叫做AbstractStringBuilder的类,下文会讨论这个类,还有就是实现了可串行和字符序列接口,没什么可说的。
内部数据结构
String和这两个可变长字符串在内部用于存储字符串的数据结构都是char数组,只不过String的char数组是由final修饰了,一旦赋值不能被修改,而这两个类的字符串都是由普通char数组实现的,见下面源码:
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence {
/** The value is used for character storage. */
private final char value[];
abstract class AbstractStringBuilder implements Appendable, CharSequence {
/**
* The value is used for character storage.
*/
char[] value;
AbstractStringBuilder类介绍
这个类是一个抽象类,我们知道StringBuffer是线程安全的,StringBuilder是非线程安全的,而两个类的功能除了这点区别之外,并没有区别,通过查看源码可知,两个类中几乎所有的方法都是拿了AbstractStriingBuilder的实现,譬如常用的字符串追加和扩容等方法,如下所示:
AbstractStringBuilder的append方法:
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;
}
接下来依次是StringBuffer和StringBuilder的append方法:
@Override
public synchronized StringBuffer append(String str) {
toStringCache = null;
super.append(str);
return this;
}
@Override
public StringBuilder append(String str) {
super.append(str);
return this;
}
都是调用了super方法,另外,除了一个是同步方法一个不是同步方法之外,还有一个区别就是StringBuffer中多了一个toStringCache这个东西,这个东西是StringBuffer中的一个char数组,用来缓存最近一次的toString,当然,每次对字符串进行更改,这个缓存都要作废,在下一次toStrinig后进行重新缓存,其声明如下:
/**
* A cache of the last value returned by toString. Cleared
* whenever the StringBuffer is modified.
*/
private transient char[] toStringCache;
就总的而言,StringBuffer和StringBuilder就两方面的不同,一是StringBuffer是线程安全的,StringBuilder是非线程安全的,二是两者的toString方法不同
toString比较
StringBuffer的toString方法如下:
@Override
public synchronized String toString() {
if (toStringCache == null) {
toStringCache = Arrays.copyOfRange(value, 0, count);
}
return new String(toStringCache, true);
}
StringBuilder的toString方法如下:
@Override
public String toString() {
// Create a copy, don't share the array
return new String(value, 0, count);
}
两个toString都是每次生成字符串都是新生成一个字符串,即使字符串没有更改,两次生成的字符串也是不同的两个。再有一点不同就是,StringBuffer的toString方法调用了一个带有两个参数的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;
}
那直接使用字节数组进行String的创建是怎么完成的呢?
/**
* Allocates a new {@code String} that contains characters from a subarray
* of the character array argument. The {@code offset} argument is the
* index of the first character of the subarray and the {@code count}
* argument specifies the length of the subarray. The contents of the
* subarray are copied; subsequent modification of the character array does
* not affect the newly created string.
*
* @param value
* Array that is the source of characters
*
* @param offset
* The initial offset
*
* @param count
* The length
*
* @throws IndexOutOfBoundsException
* If the {@code offset} and {@code count} arguments index
* characters outside the bounds of the {@code value} array
*/
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);
}
比较这两个构造器就会发现,StringBuffer的构造器在构造String的时候并没有复制字节数组而是直接指向的,StringBuilder是复制了一遍,从这一点来说,StringBuffer的toString方法性能更好。
区别总结
1.(相同)StringBuffer和StringBuilder有完全相同的继承关系并且在功能上没有什么区别,而且内部方法的实现几乎全部都是使用了相同的实现,都是继承自AbstractStringBuilder
2.(不同点)StringBuffer是线程安全的,StringBuilder是非线程安全的,由于方法的实现几乎相同,实现线程安全与否,取决于对父类方法封装的时候,是使用了同步方法还是普通方法
3.(不同点)StringBuffer的toString方法的性能比StringBuilder的更好
AbstractStringBuilder关键方法解析
1.扩容
/**
* This implements the expansion semantics of ensureCapacity with no
* size check or synchronization.
*/
void expandCapacity(int minimumCapacity) {
int newCapacity = value.length * 2 + 2;
if (newCapacity - minimumCapacity < 0)
newCapacity = minimumCapacity;
if (newCapacity < 0) {
if (minimumCapacity < 0) // overflow
throw new OutOfMemoryError();
newCapacity = Integer.MAX_VALUE;
}
value = Arrays.copyOf(value, newCapacity);
}
主要工作有两个:一是先将原来的数组长度乘二加二和指定容量相比,取大的作为实际要扩容到的容量,二是进行数组的复制。关于这个Array.copyOf方法,会在其他博文中探讨,OK,先简单说明以下,这个数组拷贝的方法速度是比较快的,是用了本地方法实现的,但是注意是浅拷贝
2.字符串追加
/**
* Appends the specified string to this character sequence.
* <p>
* The characters of the {@code String} argument are appended, in
* order, increasing the length of this sequence by the length of the
* argument. If {@code str} is {@code null}, then the four
* characters {@code "null"} are appended.
* <p>
* Let <i>n</i> be the length of this character sequence just prior to
* execution of the {@code append} method. Then the character at
* index <i>k</i> in the new character sequence is equal to the character
* at index <i>k</i> in the old character sequence, if <i>k</i> is less
* than <i>n</i>; otherwise, it is equal to the character at index
* <i>k-n</i> in the argument {@code str}.
*
* @param str a string.
* @return a reference to this object.
*/
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;
}
OK,也是有两件事儿:一是判断剩余空间够不够用了,如果不够用就要扩容,如果够用直接追加。使用的时候,有一个性能和空间占用的权衡,因为扩容是耗时耗性能的操作,如果容量太小(默认是16char)每次追加都要扩容的话,性能就很底下咯,如果一开始就大致确定了字符串大小,虽然耗空间,但是省时间,权衡
3.append和字符串连接符『+』性能比较
说白了字符串连接符『+』被编译之后,使用的还是append方法,而且是StringBuilder的,并且每次连接都要创建一个StringBuilder对象然后进行append,由此说来,字符串连接符的有点就是编写代码的时候方便,当然,有一种情况例外,譬如:
String s1 = "hello"+" "+"world";
这个编译完之后就连接好了,对于非这种情况下的,都是上面解释的那样,使用append方法
使用『+』的缺点也很明显,除了书写方便并没有什么优点,主要缺点有两个:
1.如果有大量的字符串连接符的使用,会造成大量StringBuilder类的创建,导致性能低下
2.由于创建StringBuilder类是内部完成的,如果被连接的字符串超过了16char,则在连接的时候还要进行扩容,性能更低
记忆顺序
虽说是理科生,而且死记硬背也是不提倡的,但是对于这种功能类似或相同的类,使用一定的记忆顺序能够避免很多麻烦,可以较快的记住所学的内容,毕竟学到就要记到嘛,顺序如下:
实现原理+相同点+不同点+使用注意事项