3.String、StringBuilder、StringBuffer:性能差别多大?
String、StringBuilder、StringBuffer:性能差别多大?
🚀 高频指数:★★★★★
🎯 你将收获:字符串常量池机制、不可变性的原理、三者底层结构与性能对比、项目实战场景。
一、面试常见问法
💬 面试官:
- String 为什么是不可变的?
- StringBuilder 和 StringBuffer 区别?
- 你会在什么场景下使用它们?
答不好这几句,面试官马上能看出你是否真正理解 Java 内存模型与线程安全。
二、String 的不可变性
🔹 源码结构(JDK 8)
public final class String implements java.io.Serializable, Comparable<String> {private final char value[];
}
final修饰类 → 不可继承;value[]是final→ 字符数组地址不可变;- 内部方法不会改变原数组,而是生成新对象。
🔹 设计目的
- 安全性:字符串常用于类加载、反射、HashMap key;
- 线程安全:不可变对象天然线程安全;
- 缓存优化:可放入字符串常量池重复利用。
☕️ 一句话记忆:“安全、共享、缓存”造就不可变性。
三、字符串常量池机制(String Constant Pool)
String s1 = "hello";
String s2 = "hello";
System.out.println(s1 == s2); // true
- 常量
"hello"存放于 字符串常量池(JDK7 以前在永久代,JDK8 移至堆)。 - 相同字面量只存一份,节省内存。
🔹 注意点:new String()
String s1 = "abc";
String s2 = new String("abc");
System.out.println(s1 == s2); // false
"abc"是常量池对象;new String()在堆中新建一个副本。
四、StringBuilder 与 StringBuffer
| 对比项 | String | StringBuilder | StringBuffer |
|---|---|---|---|
| 可变性 | 不可变 | 可变 | 可变 |
| 线程安全 | 安全(因不可变) | ❌ 不安全 | ✅ 安全(同步) |
| 性能 | 低(频繁创建新对象) | 高 | 略低于 StringBuilder |
| 关键修饰 | final | 无同步 | synchronized |
| 适用场景 | 少量字符串 | 单线程大量拼接 | 多线程拼接 |
🔹 核心源码片段
// StringBuilder.append()
public StringBuilder append(String str) {super.append(str);return this;
}// AbstractStringBuilder
public AbstractStringBuilder append(String str) {ensureCapacityInternal(count + len);str.getChars(0, len, value, count);count += len;return this;
}
内部维护
char[] value,通过扩容策略动态拼接。StringBuffer 在方法上加了
synchronized,因此多线程安全,但性能稍慢。
五、性能测试(简化示例)
public static void main(String[] args) {long start, end;start = System.currentTimeMillis();String s = "";for (int i = 0; i < 50000; i++) {s += i;}end = System.currentTimeMillis();System.out.println("String: " + (end - start));start = System.currentTimeMillis();StringBuilder sb = new StringBuilder();for (int i = 0; i < 50000; i++) {sb.append(i);}end = System.currentTimeMillis();System.out.println("StringBuilder: " + (end - start));start = System.currentTimeMillis();StringBuffer sbf = new StringBuffer();for (int i = 0; i < 50000; i++) {sbf.append(i);}end = System.currentTimeMillis();System.out.println("StringBuffer: " + (end - start));
}
输出结果(不同机器略有差异):
String: 1800 ms
StringBuilder: 12 ms
StringBuffer: 20 ms
⚡️ 结论:频繁拼接字符串时,
StringBuilder性能优势极大。
六、面试官追问清单
| 问题 | 答题要点 |
|---|---|
| 为什么 String 是不可变的? | 因为 value[] 被 final 修饰,引用不可变。 |
| StringBuilder 与 StringBuffer 区别? | 前者非线程安全、性能更高;后者加了同步锁。 |
| StringBuilder 线程安全吗? | 否,不能在多线程共享。 |
| 常量池位置在哪? | JDK8 之后在堆中。 |
| String + 拼接为什么慢? | 每次 + 都创建新对象。 |
七、口诀记忆
☕️ “String 稳,Builder 快,Buffer 稳又慢。”
补充版:
“常量池省空间,final 保安全;单线程用 Builder,多线程用 Buffer。”
八、项目实战案例
场景:日志拼接、SQL 动态生成、报文组装。
StringBuilder sql = new StringBuilder("SELECT * FROM user WHERE 1=1");
if (age != null) sql.append(" AND age=").append(age);
if (name != null) sql.append(" AND name='").append(name).append("'");
若使用 String 拼接,会频繁创建对象;
若多线程同时组装日志,应使用StringBuffer或加锁。
九、小结
| 知识点 | 要点 |
|---|---|
| String 不可变 | 内部 final char[] |
| 常量池优化 | 相同字面量复用 |
| StringBuilder 可变高性能 | 单线程使用 |
| StringBuffer 加锁安全 | 多线程拼接 |
✅ 口诀复盘:“单线快,双线锁,字符串常量池共享。”
