【Java面试笔记:基础】5.String、StringBuffer、StringBuilder有什么区别?
1. String、StringBuffer 和 StringBuilder 的区别
String:
- 不可变性:
String
是不可变类,一旦创建,其内容不能被修改。任何对字符串的操作(如拼接、裁剪等)都会生成新的String
对象。String str = "Hello"; str += " World"; // 创建新对象,原对象仍为"Hello"
- 线程安全:由于不可变性,
String
是天然线程安全的。 - 内存机制:字符串常量池复用相同值的字符串,节省内存。
- 应用场景:适用于字符串内容不经常变化的场景,如常量声明、少量的字符串拼接操作等。
StringBuffer:
- 可变性:
StringBuffer
是可变的字符序列,允许在已有序列的末尾或指定位置添加字符串。StringBuffer sb = new StringBuffer("Hello"); sb.append(" World"); // 直接修改原对象,无需创建新实例
- 线程安全:
StringBuffer
是线程安全的,通过在方法上加synchronized
关键字实现线程同步。public synchronized StringBuffer append(String str) { ... }
- 性能开销:同步锁导致多线程安全,
StringBuffer
的性能开销较大。单线程下性能低于StringBuilder
- 应用场景:适用于多线程环境下频繁进行字符串拼接的场景。
StringBuilder:
- 可变性:
StringBuilder
也是可变的字符序列,功能与StringBuffer
类似。StringBuilder sb = new StringBuilder("Hello"); sb.append(" World"); // 非同步操作,性能更高
- 非线程安全:
StringBuilder
去掉了线程安全机制,性能更高。单线程下比StringBuffer快约10%~15% - 应用场景:适用于单线程环境下频繁进行字符串拼接的场景,是进行字符串拼接的首选。
2. 字符串设计和实现考量
- 不可变性:
String
是典型的不可变类,所有属性都是final
的,保证了线程安全。 - 线程安全机制:
StringBuffer
通过synchronized
关键字实现线程安全,适合多线程环境。 - 性能优化:
StringBuilder
去掉了线程安全机制,减少了性能开销,适合单线程环境。 - 内存占用:
String
操作不当可能会产生大量临时对象,增加内存占用。
3. 字符串缓存
intern()
方法:String
提供了intern()
方法,用于缓存字符串。如果字符串常量池中已经存在相同的字符串,则返回常量池中的实例;否则,将字符串放入常量池。- 字符串常量池:Java 6 以后,字符串常量池被放置在堆中,避免了永久代占满的问题。
G1 GC
下的字符串排重:JDK 8u20
之后引入了G1 GC
下的字符串排重功能,通过JVM
参数-XX:+UseStringDeduplication
开启。
4. String 自身的演化
Compact Strings
:Java 9 引入了Compact Strings
设计,将字符串的存储方式从char
数组改为byte
数组加上编码标识,减少了内存占用。- 性能优化:
Compact Strings
设计带来了更小的内存占用和更快的操作速度。
5. 编码相关问题
getBytes()
和 String(byte[] bytes)
:这些方法隐含使用平台默认编码,可能导致乱码问题。建议在使用时明确指定编码方式。
6. 内存与设计考量
- String常量池:相同值的字符串共享内存,适合重复使用。
String s1 = "Java"; String s2 = "Java"; // 复用常量池中的"Java"
- StringBuilder/Buffer初始化:预估容量减少扩容次数。
StringBuilder sb = new StringBuilder(1024); // 预设容量为1024
7. 使用场景总结
场景 | 推荐类 | 理由 |
---|---|---|
字符串常量(如配置项) | String | 不可变性保证数据安全,常量池优化内存 |
单线程循环拼接字符串 | StringBuilder | 避免同步开销,性能最优 |
多线程共享字符串操作 | StringBuffer | 同步保证线程安全 |
SQL语句拼接、JSON生成 | StringBuilder | 单线程操作,高效且无需线程安全 |