String的值是不可变的,也就说,你每次改变String对象的值,都会产生新的内存空间。那么为什么会这样呢?我们先避而不谈。----问题1
StringBuffer、StringBuilder是可变字符序列,改变字符串的值,不会产生新的内存空间,但是一种情况例外,缓冲区空间不足时,会自动扩充。那StringBuffer和StringBuilder具体是如何工作的呢?----问题2
另外,StringBuffer是线程安全的,StringBuilder是非线程安全的,在非并发环境下,StringBuilder的效率更高。总结一下用法,String主要是标识变量所属的类型,只是简单地改变的情况下使用,如果是较复杂的变换,优选 StringBuilder,而如果在并发环境下时,最好使用StringBuffer。 我们又可以提出几个问题了,首先什么是线程安全和非线程安全?----问题3
为什么说StringBuilder是线程安全的,StringBuilder是非线程安全的?----问题4
为什么说StringBuilder效率比StringBuffer高一些?—问题5
【镜头一】String为什么不可变?
//String public final class String { private final char value[]; public String(String original) { // 把原字符串original切分成字符数组并赋给value[]; } } //StringBuffer public final class StringBuffer extends AbstractStringBuilder { char value[]; //继承了父类AbstractStringBuilder中的value[] public StringBuffer(String str) { super(str.length() + 16); //继承父类的构造器,并创建一个大小为str.length()+16的value[]数组 append(str); //将str切分成字符序列并加入到value[]中 } }从源码可以看出,String中的是常量(final)数组,只能被初始化一次。
StringBuffer中是个普通的数组,并且可以通过append()方法追加。也就是说可变和不可变是指String、StringBuffer中的value[]数组的可变性。
【镜头二】StringBuffer、StringBuilder缓冲区问题?
StringBuffer sb = new StringBuffer(""); System.out.println(sb.length());//lenth=0 System.out.println(sb.capacity());//capacity=16 StringBuffer sb2 = new StringBuffer("abcd"); System.out.println(sb2.length());//lenth=4 System.out.println(sb2.capacity());//capacity=20 sb2.append("abcdefghijklmnopq"); System.out.println(sb2.length());//lenth=21 System.out.println(sb2.capacity());//capacity=42 StringBuffer sb3 = new StringBuffer("a"); sb3.append("abcdefghijklmnopqabcdefghijklmnopqrstuvwxyz"); System.out.println(sb3.length());//lenth=44 System.out.println(sb3.capacity());//capacity=44 sb3.append("abc"); System.out.println(sb3.length());//lenth=47 System.out.println(sb3.capacity());//capacity=90补充一个小知识点,StringBuffer默认的缓冲区大小是初始化的字符串长度+16,之后每次扩充都是(capacity+1)*2,如果一次扩充还不够的话,缓冲区就会设置为lenth实际的大小,之后任然依照一般扩充原则。
【镜头三】什么是线程安全和非线程安全?
线程安全性问题必然涉及到多线程并发程序。通常程序中的不同线程需要访问共享变量或共享空间,这个时候,就需要合理“调度”线程访问的先后顺序,否则数据就乱套了。所谓线程安全,前提是并发环境,不同的线程能够按照正确的顺序获取到共享空间的值,使程序正确地执行,换句话说,就是不违背“原子性、有序性、可见性”三个原则。
原子性:一个或多个操作要么全部执行且中间不中断,要么全部不执行。经典的例子:客户A像客户B转账1000元,这里面可以分为客户A的账户减少1000和客户B的账户增加1000,要么两个操作都执行,要么都不执行,不会打破现实的平衡,最多是影响客户体验。
有序性:程序最终执行的结果应该和代码所编写的顺序的执行结果一致。JVM可能会有指令重排序的优化措施,但是这种重排序并不会影响程序最终的执行结果。
又是题外的知识了,那么什么样指令可以重排序呢?如果指令1和指令2没有数据依赖,就可能被重排序。
int i = 0; boolean flag = false; i = 1; //语句1 flag = true; //语句2从上面的代码就可以看出,i 和 flag就没有依赖性,哪条指令先执行,后执行都不会影响程序最终的结果。当然这只是在单线程中,如果是并发程序,那么多余共享变量,我们就需要加锁来保证有序性原则。
可见性:当某一个线程对共享变量做过更改之后,其他的线程应该知道这种改变,即其他线程应该拿到修改后的值。
【镜头四】为啥说StringBuffer是线程安全,StringBuilder是非线程安全?
StringBuffer的很多方法都有synchronized关键字修饰,而StringBuilder没有。简单解释一下synchronized:每一个类对象都对应一把锁,当线程A调用类对象O中的synchronized修饰的方法M时,必须获得对象O的锁才能执行方法M,否则线程A阻塞。一旦线程A开始执行方法M,锁就被独占了,其他想要调用对象O的M方法的线程阻塞。只有等线程A执行完方法M,释放锁之后,阻塞线程才有机会去调用方法M。 由于String是不可变的,所以是线程安全的。
【镜头五】String、StringBuffer、StringBuilder的效率比较? StringBuffer、StringBuilder在大多数情况下都比String效率要高,因为String在做字符串拼接的时候需要创建新的字符串对象,而StringBuffer、StringBuilder通过append进行扩展。StringBuilder>StringBuffer,这个其实也比较明显,两者的差异也就在于synchronized。
参考: https://blog.csdn.net/itchuxuezhe_yang/article/details/89966303