从零开始学Java之StringBuilder与StringBuffer使用及源码解读
String为什么设计为不可变?如何保证String不可变?
一旦一个String
对象被创建,其存储的文本内容就不能被改变。这是因为:
- 让并发编程变得更简单。引起线程安全问题的根本原因在于:多个线程需要同时访问同一个共享资源,修改资源,容易出现问题。如果共享资源在创建之后就完全不再变更,如同一个常量,有修改则返回一个新对象,则可以保证线程安全
- 不可变的对象因为状态不会改变,所以更容易进行缓存和重用。字符串常量池的出现正是基于这个原因。当代码中出现相同的字符串字面量时,JVM会确保所有的引用都指向常量池中的同一个对象,从而节约内存。
- 因为String的内容不会改变,所以它的哈希值也就固定不变。这使得String对象特别适合作为
HashMap
或HashSet
等集合的键,因为计算哈希值只需要进行一次,提高了哈希表操作的效率。
保证String不可变的措施:
- String类内部使用一个私有的字符数组来存储字符串数据。这个字符数组在创建字符串时被初始化,之后不允许被改变。
- String类也不提供任何可以修改其内容的公共方法,像
concat
这些看似修改字符串的操作,实际上都是返回一个新创建的字符串对象,而原始字符串对象保持不变。 - String类本身被声明为
final
,这意味着它不能被继承,防止通过子类添加修改方法来改变字符串内容的可能。
StringBuilder 和 StringBuffer
StringBuilder
和StringBuffer
定义的字符串内容可变。
当遇到大量字符串连接时,由于String的不变性,每次都是返回一个新的String对象,因此用StringBuilder
更合适,它不会生成很多新的对象,StringBuffer
和StringBuilder
类似,但每个方法上都加了synchronized
关键字,所以是线程安全的。
- String:适用于字符串内容不会改变的场景,比如说作为
HashMap
的key - StringBuilder:适用于单线程环境下需要频繁修改字符串内容的场景,比如在循环中拼接或修改字符串,是String的完美替代品
- StringBuffer:现在已经不怎么用了,因为一般不会在多线程场景下去频繁的修改字符串内容
StringBuilder
和StringBuffer
均继承自抽象类AbstractStringBuilder
,其append
方法最终是调用父类的append
:
以StringBuilder为例
而父类的append将字符串放入其字符数组中,同时记录字符数量的count
也同步增加。其中ensureCapacityInternal
方法是用来确保字符数组能放得下新加的字符串,当数组大小不够时会扩容。
若原有容量oldCapacity
小于需要的容量minimumCapacity
,就会利用Arrays.copyOf()
方法,将当前数组的值拷贝给newCapacity()
个长度的新数组,最后再重新赋值给value字节数组。因此是利用数组复制的方法来实现扩容。
newCapacity
方法作用是计算出扩容后新数组的长度,若大于整型的最大值则抛出OutOfMemoryError
。