从源码上看StringBuilder和StringBuffer的区别

在开发中,经常能碰到大量字符串的拼接操作,通常我们会使用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则不会出现这个问题。