Java中的String

从零开始学Java之StringBuilder与StringBuffer使用及源码解读

String为什么设计为不可变?如何保证String不可变?

一旦一个String对象被创建,其存储的文本内容就不能被改变。这是因为:

  • 让并发编程变得更简单。引起线程安全问题的根本原因在于:多个线程需要同时访问同一个共享资源,修改资源,容易出现问题。如果共享资源在创建之后就完全不再变更,如同一个常量,有修改则返回一个新对象,则可以保证线程安全
  • 不可变的对象因为状态不会改变,所以更容易进行缓存和重用。字符串常量池的出现正是基于这个原因。当代码中出现相同的字符串字面量时,JVM会确保所有的引用都指向常量池中的同一个对象,从而节约内存。
  • 因为String的内容不会改变,所以它的哈希值也就固定不变。这使得String对象特别适合作为HashMapHashSet等集合的键,因为计算哈希值只需要进行一次,提高了哈希表操作的效率。

保证String不可变的措施:

  • String类内部使用一个私有的字符数组来存储字符串数据。这个字符数组在创建字符串时被初始化,之后不允许被改变。
  • String类也不提供任何可以修改其内容的公共方法,像concat这些看似修改字符串的操作,实际上都是返回一个新创建的字符串对象,而原始字符串对象保持不变。
  • String类本身被声明为final,这意味着它不能被继承,防止通过子类添加修改方法来改变字符串内容的可能。

StringBuilder 和 StringBuffer

StringBuilderStringBuffer定义的字符串内容可变。

当遇到大量字符串连接时,由于String的不变性,每次都是返回一个新的String对象,因此用StringBuilder更合适,它不会生成很多新的对象,StringBufferStringBuilder类似,但每个方法上都加了synchronized关键字,所以是线程安全的。

  • String:适用于字符串内容不会改变的场景,比如说作为HashMap的key
  • StringBuilder:适用于单线程环境下需要频繁修改字符串内容的场景,比如在循环中拼接或修改字符串,是String的完美替代品
  • StringBuffer:现在已经不怎么用了,因为一般不会在多线程场景下去频繁的修改字符串内容

StringBuilderStringBuffer均继承自抽象类AbstractStringBuilder,其append方法最终是调用父类的append

append

以StringBuilder为例

append

append

而父类的append将字符串放入其字符数组中,同时记录字符数量的count也同步增加。其中ensureCapacityInternal方法是用来确保字符数组能放得下新加的字符串,当数组大小不够时会扩容。

append

若原有容量oldCapacity小于需要的容量minimumCapacity,就会利用Arrays.copyOf()方法,将当前数组的值拷贝给newCapacity()个长度的新数组,最后再重新赋值给value字节数组。因此是利用数组复制的方法来实现扩容

append

newCapacity方法作用是计算出扩容后新数组的长度,若大于整型的最大值则抛出OutOfMemoryError