一、概述(知识点,源码分析在下文)
String / StringBuffer / StringBuilder 是三个比较常见的类,都可以用来存放字符串但是三者有很多不同,本文从String的不可变性,三者效率、线程安全、StringBuffer和StringBuilder的扩容机制以及如何选择三者进行阐述:
1.三者的底层都是使用char[]数组进行数据的存放的
2.StringBuffer是线程安全的,StringBuilder是线程不安全的
3.String是不可变的,StringBuffer和StringBuilder则是可变的
4.三者的效率从高到低排列:StringBuilder > StringBuffer > String
5.StringBuffer和StringBuilder底层默认构建长度为16的数组
6.StringBuffer和StringBuilder当字符串长度大于底层存放字符的数组的长度时就会触发扩容机制(扩容为原来的2倍+2)
二、源码解析之不可变性:
String str = "abc";
str = "edf";
一般情况下我们使用String就是赋值或者是对str进行修改,那么这样来说String不是可变的吗?为什么说String是不可变的呢? 这里我们理解的“可变”指的是str变量可以指向新的字符串(底层char[]数组),“不可变”指的是原先存放的"abc"字符串的char[]数组是不可变的,是不可以在原先字符串的char[]数组进行修改,所以如果更改了String的值,那么在内存中一定是创建了新的字符串。
从下图就可以看出,当str被赋值为"edf"的时候,是不会改变原先的"abc",而是创建了新的字符串"edf",同时"abc"已经没有任何引用指向了,可以被当成垃圾回收了,所以在String中也没有对字符串的append、remove方法(StringBuffer和StringBuilder则有),只有重新赋值操作。
从JDK中的String.class可以看出String中使用的是char[]存放字符串,在使用String str = "abc"或者是String str = new String("abc")的时候实际上就会在底层创建private final char[] value(由于被final和private修饰,所以即为不可变的),即无法对str本身的字符数组进行修改,若“修改字符串”那么必定创建新的char[] value并重新赋值
StringBuffer和StringBuilder则是大致是相同的,底层也是使用char[] value数组,但是该数组是没有使用final和private修饰,在对字符串进行操作的时候实际上是对原先的字符串操作
下面这段代码也就体现了StringBuilder和StringBuffer的可变性,可以在原先的str基础上进行修改,实际上内存并不会创建新的字符串,而是在原有的字符串进行了修改。所以在StringBuilder和StringBuffer中提供了append(追加)/replace(修改)等方法来对字符串进行修改。
StringBuffer str= new StringBuffer("abc");
str.replace(0,3,"cde");
三、源码解析之线程安全:
线程安全问题出现在:多个线程对共享数据同时进行操作时候出现数据不一致的问题
1.在String中是无法对底层的字符串就行修改的(不可变性),所以可以理解为String没有线程安全问题(狭义上理解)
2.StringBuffer和StringBuilder都是可以对字符串进行修改的,那么多个线程同时对同一个字符串进行修改那么就可能出现线程不安全的问题
3.在StringBuffer类中基本所有方法都使用了synchronized线程同步锁来保证线程安全,在StringBuilder则没有使用同步锁,所以StringBuffer是线程安全的,StringBuilder则不是
StringBuffer和StringBuilder的底层代码基本是一致的,但是StringBuffer的方法都加上了synchronized同步锁,这样就可以保证多个线程同时对共享数据操作的时候保证了数据的安全,也是因为StringBuffer保证了线程安全所以其工作效率会比StringBuilder低下
四、三者的效率分析
三者的效率从高到低排列如下:(原因在下文可见)
StringBuilder > StringBuffer > String
原因:
1.首先String无疑效率是最低的,由于String的不可变性,所以每次修改String的值无疑是在内存中重新创建了新的数组来存放所以String效率最低
2.StringBuffer由于使用了synchronized锁机制来保证线程安全,所以效率比StringBuilder要低
五、StringBuffer和StringBuilder的扩容机制
无参构造器
StringBuilder str = new StringBuilder();
从源码可以看出,StringBuffer和StringBuffer在使用无参构造器创建对象的时候,默认在底层创建的是长度为16的char[] value
带参构造器
StringBuilder str = new StringBuilder("abc");
如果是使用带参数的构造器,则底层构建的数组的长度为:16+参数字符串的长度(16+"abc".length() = 19)
扩容机制:2n+2
StringBuffer和StringBuilder不断的使用append(str)完字符串中添加字符的时候, 当然底层的数组长度不够用的时候就会触发扩容机制,从源码可以看出扩容为:2倍+2
六、String/StringBuilder/StringBuffer如何选择
1.当可以确定字符是不需要改变的或者修改次数很少那么选择String
2.如果需要频繁的进行字符串的修改操作,那么从StringBuffer和StringBuilder中选择
(因为StringBuffer和StringBuilder在修改的时候可以避免在内存中大量的创建新的字符串)
3.优先选择StringBuilder,因为其效率最高,当需要保证线程安全问题的时候,那么使用StringBuffer
4.当大致知道整个字符串的长度的时候,那么就可以使用StringBuilder带参数的构造器,那么就会在构造的时候之间创建指定长度的数组,避免了不断扩容导致底层数组的不断复制