1、典型回答
String、StringBuilder 和 StringBuffer 都是 Java 语言中,用于操作字符串的类,但它们在性能、可变性和线程安全性方面有一些区别
1、String:不可变字符串类,也就是说一旦创建,它的值就不可变。每次对 String 进行拼接、裁剪等操作时,都会创建一个新的 String 对象,所以它的这些操作效率不高,但由于其不可变性,String 可以保证线程安全,适用于字符串不经常变化的场景
2、StringBuffer:线程安全的可变字符串类,它解决了 String 字符串拼接、裁剪的问题,它提供了对字符串进行操作的 append 和 insert 等方法,可以将字符串添加到末尾或指定位置,并且它不会创建新字符串。并且 StringBuffer 是线程安全的,适用于多线程环境下对字符串的操作,其诞生于 JDK1.0
3、StringBuilder:非线程安全的可变字符串类,与StringBuffer 类似,但是它去掉了线程安全的部分,有效减小了开销,是绝大部分情况下进行字符串拼接的首选,它诞生于JDK1.5
2、全面剖析
将 String 设计为不可变的,可以更好用于缓存、保证线程安全、并提高程序的性能,但它的副作用就是在字符串拼接和裁剪的时候总要创建新字符串,性能不高,因此字符串变化的场景中要使用 StringBuffer 来替代,但 StringBuffer 底层使用了 synchronized 修饰在 append 等拼接方法上,如下源码所示:
所以,StringBuffer 效率也不是很高,于是在 JDK 1.5 的时候就有了去掉锁,但功能和 StringBuffer 完全相同的 StringBuilder
3、知识扩展
关于字符串还有几个问题比较重要:
- StringBuffer 和 StringBuilder 有什么关系?它们的底层是如何实现的?
- intern() 方法的作用是啥?为什么要用 intern() 方法?它有什么副作用么?
3.1、StringBuilder 和 StringBuffer 底层实现
属于 “亲兄弟”的关系,因为它们都继承了同一个类 AbstractStringBuilder ,如下图所示:
String 的底层是使用数组实现的,而 StringBuilder 和 StringBuffer 的底层也是使用数组实现的,只是 StringBuilder 和 StringBuffer 的数组并未加 final 修饰,实现源码如下:
3.2、intern 方法说明
String 的 intern() 方法用于将字符串添加到字符串常量池中,并返回常量池中对应的字符串对象的引用
常量池是Java 运行时环境中的一个特殊存储区域,用于存储在编译期间确定的字符串常量和符号引用。当调用String的 intern() 方法时,如果常量池中已经存在相同值的字符串,则返回对应的引用;如果常量池中不存在,则在常量池中创建并返回对应的引用
例如以下代码:
String 创建对象的方式中,直接字符串赋值也就是 str1 的方式会默认将在常量池中创建对象,而new 对象的方式也就是str2的方式则是在堆上创建的
intern 方法的副作用主要有以下两个:
- 代码中大量使用 intern 方法会使得代码臃肿,且不易维护
- 使用 intern 方法会增加方法区的内存占用,而老版本的方法区是存储在永久代中的(JDK1.7之前),而永久代的空间又很小,因此如果 intern 方法使用不当,就会导致频频进行垃圾回收,所以在老版本要慎用 intern 方法