在Java中,关于字符串类分为两种,一种是上篇博客讲的String类,即不可变字符串类,另外一种则是可变字符串类,即AbstractStringBuilder抽象类的子类,StringBuffer与StringBuilder类,其中的两者的区别体现于如下代码:
StringBuilder builder=new StringBuilder("123");
System.out.println(builder==builder.append("456")); //true
String s=new String("111");
System.out.println(s==s.concat("222"));//false
当两者追加一个字符或者字符串的时候,AbstractStringBuilder类的地址是不变的,而String类是总是改变的,也就是String总会创建一个新的String类,关于AbstractStringBuilder类追加字符串是仅扩充其内部的存贮字符的value数组的大小,而整个实例的地址是不变的,源码如下:
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;
}
private void ensureCapacityInternal(int minimumCapacity) {
// overflow-conscious code
if (minimumCapacity - value.length > 0) {
value = Arrays.copyOf(value,
newCapacity(minimumCapacity));
}
}
从源码中可以看出,AbstractStringBuilder 类追加操作底层调用了java.lang包下的Arrays类的copyOf方法来进行扩充数组以及复制的,最后返回this变量,返回自身。而AbstractStringBuilder 类不仅提供了value数组的扩充操作,也提供了缩小的操作:
public void trimToSize() {
if (count < value.length) {
value = Arrays.copyOf(value, count);
}
}
而newCapacity方法每次扩充的大小是原来的value数组长度的2倍+2:
private int newCapacity(int minCapacity) {
// overflow-conscious code
int newCapacity = (value.length << 1) + 2;
if (newCapacity - minCapacity < 0) {
newCapacity = minCapacity;
}
return (newCapacity <= 0 || MAX_ARRAY_SIZE - newCapacity < 0)
? hugeCapacity(minCapacity)
: newCapacity;
}
abstract class AbstractStringBuilder implements Appendable, CharSequence {
char[] value;
int count;
}
其中在AbstractStringBuilder 类中count属性是用来记录value数组中所用空间的长度,也就是代表着字符串的长度,而在String类中是没有这个属性的,String类的value数组的大小是根据所要保存的字符串的长度而固定的。整个AbstractStringBuilder 类实现了String类中的功能,关于AbstractStringBuilder类与StringBuffer、StringBuilder类关系如下:
从上图看出,他们之间是父子类的关系,而StringBuilder类则是直接实现的AbstractStringBuilder抽象类,在其内部调用的都是其父类的方法,而StringBuffer类则是在StringBuilder类的基础上考虑到了多线程的情况,它是线程安全的,就比如两者重写的toString方法:
//StringBuilder类、
public String toString() {
// Create a copy, don't share the array
return new String(value, 0, count);
}
//StringBuffer类
private transient char[] toStringCache;//起一个缓存的作用,不被序列化
public synchronized String toString() {
if (toStringCache == null) {
toStringCache = Arrays.copyOfRange(value, 0, count);
}
return new String(toStringCache, true);
}
两者都是返回一个新的String对象,StringBuffer类加了同步,让其保证是线程安全的。两者也都实现了写入读取输入流与输出流(以StringBuilder为例):
private void writeObject(java.io.ObjectOutputStream s)
throws java.io.IOException {
s.defaultWriteObject();
s.writeInt(count);
s.writeObject(value);
}
private void readObject(java.io.ObjectInputStream s)
throws java.io.IOException, ClassNotFoundException {
s.defaultReadObject();
count = s.readInt();
value = (char[]) s.readObject();
}
综合来看,三者的效率:StringBuilder>StringBuffer>String类,因为StringBuilder与StringBuffer两者实现相同,但StringBuffer类兼顾了多线程的情况所以要比StringBuilder慢一些,而String类每次更改都要创建一个新的String对象(前提常量池中没有该对象),所以要比前两者慢一些,所以它们的选取策略如下:
- 当是少量的字符串操作且是单线程时时,采用String类;
- 当是大量的字符串操作且是单线程时,采取StringBuilder类;
- 当是多线程的时候,采用StringBuffer类;