1. String:String对象一旦创建,其值是不能修改的(因为String类是被final关键字修饰的类),如果要修改,则会重新开辟内存空间来存储修改后的对象,即修改了String类的引用(生成一个新的String对象,但是原String对象是没有被改变的)。这是因为String类的底层是通过数组来存值的,数组长度不可变这一特性导致了上述问题。
如果我们在实际开发中需要对某个字符串进行频繁地修改,使用String类就会造成内存空间的浪费,此时可以使用StringBuffer类来解决这个问题。
2. StringBuffer:StringBuffer的底层也是通过数组来存值的,并且数组的默认长度是16,即一个空的StringBuffer对象的数组长度是16,实例化一个StringBuffer对象即创建了一个大小为16个字符的字符串缓冲区。但是当我们调用有参构造函数来创建一个StringBuffer对象时,数组长度就不再是16了,而是根据当前对象的值来决定数组的长度,此时数组的长度为“当前对象值的长度+16”。所以当一个StringBuffer对象创建完成之后,将会有16个字符的空间可以对其值进行修改。但是如果修改的值的范围超过了16个字符,就会先检查StringBuffer对象的原字符数组的容量能不能装下新的字符串,如果不能的话,需要对原字符数组进行扩容。
那么StringBuffer是如何扩容的呢?
扩容的逻辑就是创建一个新的字符数组,将现有容量(x)扩大一倍再加上2(即2x+2),如果还是不够大则直接等于需要的容量大小。扩容完成后,再将原字符数组的内容复制到新字符数组,最后指针指向新的字符数组。
3. StringBuilder:StringBuilder类和StringBuffer类都继承自AbstractStringBuilder类,并且都实现了接口java.io.Serializable接口和CharSequence接口。
但是两者最大的区别在于StringBuffer对几乎所有的方法都实现了同步,是线程安全的,适用于多线程系统;而StringBuilder中的方法没有实现同步,是线程不安全的,适用于单线程系统。因此当需要考虑线程安全的场景下,应该使用StringBuffer类,如果不需要考虑线程安全而是追求效率的场景下可以使用StringBuilder。