最近看了极客时间的String、StringBuilder和StringBuffer,想通过记录加深一下印象。
String
-
1、字符串拼接
String 类由于他的属性被声明成final类型,所以String声明的字符串是不可变的,每次对字符串内容进行操作,他都会生成一份新的String对象。这种方式的好处就是保护了原数据不会被更改,当使用赋值拷贝构造函数时,不会产生中间对象,所以这种场景比较适合String声明字符串。但是坏处也是显而易见的,如果出现大量的对字符串的操作,这种方式就会产生大量的需要回收的垃圾对象。所以不适合有很多对字符串操作的场景。基于此种情况,Java也对其进行了优化,比如对$+$号进行了重载,举个例子:
1 |
|
在jdk8版本里,字符串的拼接会转化成StringBuilder.append操作,在jdk9以后的版本 会依靠动态代理与javac生成的字节码进行解耦。
- 2、字符串缓存 (这里之后再进行详细的了解吧)
StringBuilder和StringBuffer
StringBuilder和StringBuffer,这两个类继承自同一父类AbstractStringBuider,区别在于StringBuffer的所有方法都添加了synchronized关键字,是所有操作都是线程安全的。
1 |
|
我通过对StringBuilder和StringBuffer进行相同的操作,并且进行$10000000$次,次数太少显现不出差距,不带synchronized的reverse()操作大概30多毫秒,而带synchronized的reverse()的操作大概四十多毫秒,相差10毫秒,所以除非有对线程安全的需要,不然还是使用StringBuilder较好。
StringBuilder和StringBuffer的扩容
1 |
|
StringBuilder默认初始化的大小为16,StringBuffer也是一样。
1 |
|
这是AbstractStringBuilder的append操作,其中的ensureCapacityInternal()是确保数组容量能够存储下拼接后的数组,如果不足会进行扩容。
1 |
|
其中coder是编码格式,早先的版本采用char数组存储字符,但是由于空间利用率不高,改为采用byte数组存储,但是由于不同编码格式所占用字节数不同,所以使用变量coder进行标记(这里可以通过设置VM参数-XX:-CompactStrings进行改变coder的值)。上面的ensureCapacityInternal()函数会将原来的byte数组拷贝到扩容后的数组,并将原来的数组丢弃,所以在使用StringBuilder或者StringBuffer时,如果知道大概需要多长的数组,最好提前声明好,不然后期可能有很多次扩容开销。