`String`、`StringBuilder` 和 `StringBuffer`区别卓望一面面试题
文章目录
- 1. String
- 核心特性:不可变 (Immutable)
- 应用场景(敲黑板!!考过!!)
- 优势
- 缺点
- 解决了什么问题
- 2. StringBuffer(synchronized同步锁)
- 核心特性:可变 (Mutable) + 线程安全 (Thread-Safe)
- 应用场景
- 优势
- 缺点
- 解决了什么问题
- 3. StringBuilder
- 核心特性:可变 (Mutable) + 非线程安全 (Not Thread-Safe)
- 应用场景(为什么用String,而不是StringBuilder?为什么用StringBuilder而不是String?)
- 优势
- 缺点
- 解决了什么问题
- 卓望一面面试题
- 总结与选择指南
String
、StringBuilder
和 StringBuffer
是 Java 中处理字符串的三个核心类。它们的设计和存在是为了在不同场景下平衡性能、线程安全和易用性。
下面我们来详细剖析每一个类,并回答它们分别适用于什么场景、有何优缺点,以及解决了其他类什么问题。
1. String
核心特性:不可变 (Immutable)
一旦一个 String
对象被创建,它包含的字符序列就永远不能被改变。任何对 String
对象的修改(如拼接、替换)都会导致创建一个全新的 String
对象。
应用场景(敲黑板!!考过!!)
- 存储固定不变的文本数据:如配置信息、URL、错误消息、数据库连接字符串等。
- 作为常量使用:在代码中定义的字符串字面量默认就是
String
对象。 - 需要安全性的场景:因为不可变,所以它被广泛用于网络连接、数据库用户名/密码等,防止在程序执行过程中被意外篡改。
- 作为
HashMap
的键:HashMap
的键要求是不可变的。如果键的内容在放入Map
后发生变化,将导致你再也找不到这个键对应的值。String
的不可变性完美符合这个要求。
优势
- 线程安全:由于不可变,
String
对象可以在多个线程之间自由共享,无需任何同步处理。一个线程无法改变另一个线程看到的String
对象。 - 可预测和安全:当你把一个
String
传递给一个方法时,你不用担心这个方法会改变字符串的内容。这使得代码更加健壮和安全。 - 支持字符串常量池:JVM 通过字符串常量池来缓存字符串字面量,可以复用相同的字符串对象,节省内存。例如,
String s1 = "hello"; String s2 = "hello";
s1
和s2
指向的是同一个对象。
缺点
-
性能开销大(在频繁修改时):最大的缺点就是每次修改都会创建新对象。如果在循环中进行大量字符串拼接,会产生大量临时的、无用的对象,占用内存并触发频繁的垃圾回收(GC),严重影响性能。
// 性能极差的例子 String result = ""; for (int i = 0; i < 10000; i++) {result += "hello"; // 每次循环都会创建一个新 String 对象 }
解决了什么问题
String
的设计初衷不是为了高效修改,而是为了提供一种可靠、安全的方式来表示和传递字符串数据。它的不可变性解决了在并发和复杂方法调用中数据被意外篡改的根本性安全问题。
2. StringBuffer(synchronized同步锁)
核心特性:可变 (Mutable) + 线程安全 (Thread-Safe)
StringBuffer
像一个可变的字符缓冲区。对它的修改(如 append
, insert
, delete
)是直接在原对象上进行的,不会创建新对象。它的所有公开方法(如 append
)都使用了 synchronized
关键字进行同步,保证了在多线程环境下的操作是安全的。
应用场景
- 多线程环境下的字符串拼接/修改:当多个线程需要共享并操作同一个字符串缓冲区时,
StringBuffer
是唯一的正确选择。例如,多个任务同时向一个共享的日志记录器中追加日志内容。 - 需要兼容旧代码的场景:
StringBuffer
从 JDK 1.0 就存在了,是一些早期API的默认选择(尽管现在很多已被StringBuilder
替代)。
优势
- 线程安全:这是它相对于
StringBuilder
最核心的优势。可以在多线程环境中安全使用,无需开发者手动加锁。 - 高性能(相对于String):在需要频繁修改字符串的场景下,由于其可变性,性能远高于
String
。
缺点
- 性能开销(相对于StringBuilder):因为所有方法都加了
synchronized
同步锁,即使在单线程环境下,这个同步操作也会带来不小的性能开销。
解决了什么问题
- 解决了
String
在频繁修改时性能低下的问题:通过提供一个可变的字符缓冲区,避免了创建大量临时对象。 - 提供了多线程环境下安全修改字符串的方案:这是
StringBuilder
无法做到的。
3. StringBuilder
核心特性:可变 (Mutable) + 非线程安全 (Not Thread-Safe)
StringBuilder
在功能上与 StringBuffer
几乎完全一样,也是一个可变的字符缓冲区。唯一的、也是最关键的区别在于:它的方法没有 synchronized
修饰,因此不是线程安全的。
应用场景(为什么用String,而不是StringBuilder?为什么用StringBuilder而不是String?)
- 单线程环境下的字符串拼接/修改:这是
StringBuilder
最主要、最广泛的应用场景。例如,在方法内部拼接SQL语句、构建JSON或XML字符串、循环拼接长字符串等。 - 对性能要求极高的场景:只要你能确保代码只在单个线程中运行,就应该优先使用
StringBuilder
,因为它避免了StringBuffer
的同步开销。
优势
- 极致的性能:在单线程环境下,它是三者中执行效率最高的。因为它既是可变的,又没有同步开销。
缺点
- 线程不安全:绝对不能在多个线程间共享同一个
StringBuilder
对象,否则会导致数据错乱、程序崩溃等不可预知的问题。
解决了什么问题
- 解决了
String
在频繁修改时性能低下的问题:和StringBuffer
一样,通过可变性来优化性能。 - 解决了
StringBuffer
在单线程中性能过度损耗的问题:它去掉了StringBuffer
的同步锁,将性能发挥到极致,成为单线程场景下的最佳选择。StringBuilder
是在 JDK 1.5 中被引入的,其目的就是为了替代单线程场景下的StringBuffer
。
卓望一面面试题
String s="a"+"b"+"c";// A
String s="abc";//B
StringBuilder s=new StringBuilder("abc")//C
A和B经过编译器优化之后,一样。共同点是都会编译为普通字符串常量放入字符串常量池,
底层都是通过调用了StringBuilder的append方法来完成字符串拼接(环境是jdk-7,不要用jdk8及其以上环境,会有其他优化的!!)
总结与选择指南
特性 | String | StringBuffer | StringBuilder |
---|---|---|---|
可变性 | 不可变 (Immutable) | 可变 (Mutable) | 可变 (Mutable) |
线程安全 | 安全 (Thread-Safe) | 安全 (Thread-Safe) | 不安全 (Not Thread-Safe) |
性能 | 低(频繁修改时) | 中等 | 高 |
核心机制 | 创建新对象 | synchronized 方法 | 无同步开销 |
引入版本 | JDK 1.0 | JDK 1.0 | JDK 1.5 |
如何选择?
-
字符串很少改变?
- 用
String
。例如:String greeting = "Hello, World!";
- 用
-
单线程环境下,字符串需要频繁修改/拼接?
- 用
StringBuilder
(首选)。例如:构建复杂的SQL查询语句。
StringBuilder sql = new StringBuilder("SELECT * FROM users WHERE "); if (name != null) {sql.append("name = '").append(name).append("'"); }
- 用
-
多线程环境下,字符串需要被多个线程共享并修改?
- 用
StringBuffer
。例如:一个全局计数器或日志缓冲区。
// 假设这个 buffer 是一个被多个线程共享的静态字段 private static StringBuffer sharedLogBuffer = new StringBuffer();// 某个线程在执行 public void log(String message) {sharedLogBuffer.append(message).append("\n"); }
- 用
一个简单的记忆法则:
- String:不变。
- StringBuilder:可变,单线程,速度快。
- StringBuffer:可变,多线程,安全但稍慢。