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