在开发中,经常能碰到大量字符串的拼接操作,通常我们会使用StringBuilder和StringBuffer这两个类来实现,但是他俩有什么区别呢?通常下的答案是:StringBuffer是线程安全的,StringBuilder是线程不安全的。确实如此,但是为什么呢?我们从源码分析一下。(具体源码自行查看,此处不再粘贴)
从类继承图上看,StringBuilder和StringBuffer没有太大的差别,都继承了AbstractStringBuilder这个抽象类。而StringBuilder和StringBuffer这两个类的操作基本都是调用的AbstractStringBuilder的方法,也就是说,大部分的操作都是在AbstractStringBuilder这个抽象类中实现的。
AbstractStringBuilder内部有一个char[] value数组用来保存拼接的字符串数据,只有当调用了toString方法之后才会将value这个数组转成一个String对象返回。但是AbstractStringBuilder并没有实现toString方法而是留给了他的子类去实现。
说一下StringBuilder和StringBuffer的区别:
1、StringBuilder的StringBuffer的方法都是直接调用AbstractStringBuilder的方法。
2、StringBuffer的每个方法都增加了synchronized关键字来保证线程安全。
3、StringBuffer增加了一个char[] toStringCache;数组用来做缓存,在每一次对字符串发生操作时均清空这个数组。
4、StringBuilder在toString的时候只是简单的将value数组转成String对象返回;StringBuffer在toString的时候,如果toStringCache的值为空,则会将value的值拷贝到toStringCache,如果不为空则证明没有改变过直接使用缓存。之后调用String的特殊构造方法构造String对象返回(String类中一个特殊的构造函数String(char[] value, boolean share))。
所以从源码上看,StringBuilder的实现比较简单,所以效率上来说应该会比较快,通常情况下的使用场景一般也不会有线程不安全的情况,所以通常推荐使用StringBuilder。而StringBuffer因为方法上都有synchronized来保证线程安全,所以效率上肯定比较慢,而在需要保证线程安全的情况下,也没得选择,只能选择StringBuffer。
下面是一个StringBuilder线程不安全的一个例子,可以运行试一下,具体逻辑看代码:
public class Test {
public static void main(String[] args) {
for (int i = 0; i < StringThread.times; i++) {
new StringThread(i+",").start();
}
try {
StringThread.latch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(StringThread.sb.toString());
}
}
class StringThread extends Thread {
public static int times = 10;
public static final CountDownLatch latch = new CountDownLatch(times);
public static StringBuilder sb = new StringBuilder();
// public static StringBuffer sb = new StringBuffer();
private String str;
public StringThread(String str) {
this.str = str;
}
@Override
public void run() {
//模拟业务操作耗时
try {
Thread.sleep((long) (Math.random() * 10));
} catch (InterruptedException e) {
e.printStackTrace();
}
//调用次数+1
sb.append(str);
//latch-1
latch.countDown();
}
}
执行结果:
结尾2那个地方出现了2个空格,这就出问题了。而你修改代码使用StringBuffer则不会出现这个问题。