String、StringBuilder 和 StringBuffer 的区别?
String、StringBuilder 和 StringBuffer 的区别?
zhangchangsheng面试考察点
- 基础掌握度:面试官不仅仅是想知道这三者的区别,更是想确认你是否理解 Java 字符串的不可变性设计,以及为什么需要
StringBuilder和StringBuffer。 - 线程安全意识:考察你是否清楚
StringBuilder和StringBuffer在线程安全上的差异,能否根据业务场景选择合适的类。 - 性能优化意识:是否了解字符串拼接在不同场景下的性能差异,能否写出高性能的字符串处理代码。
核心答案
| 对比项 | String | StringBuilder | StringBuffer |
|---|---|---|---|
| 可变性 | 不可变 | 可变 | 可变 |
| 线程安全 | 安全(不可变) | 不安全 | 安全(synchronized) |
| 性能 | 拼接差 | 最快 | 较快 |
| 适用场景 | 少量字符串、常量 | 单线程拼接 | 多线程拼接 |
| 出现版本 | JDK 1.0 | JDK 1.5 | JDK 1.0 |
一句话总结:单线程用 StringBuilder,多线程用 StringBuffer,常量用 String。
深度解析
一、String 的不可变性
String 是 Java 中最常用的类之一,它的核心特性是 不可变(Immutable)。
flowchart TB
A["String s = hello<br/>String 对象:value[] 为 h,e,l,l,o(final 修饰)<br/>hash:99162322(缓存哈希值)"] --> B["s = s + world<br/>看似修改,实则创建新对象"]
B --> C["原对象(等待 GC)<br/>value[] 仍为 h,e,l,l,o"]
B --> D["新 String 对象<br/>value[] 为 hello world"]
上图展示了 String 不可变性的核心原理,整体需要关注以下几点:
- 底层存储:
String内部使用final char[] value(JDK 9 之后改为byte[])存储字符数据,final修饰意味着引用不可变。 - 任何”修改”操作都会创建新对象:如拼接、截取、大小写转换等,原对象不变,返回新对象。
- 不可变的好处:
- 线程安全:多个线程可以安全共享,无需同步
- 字符串常量池优化:相同字符串只存一份
- 安全性:作为参数传递时不会被修改,适合作为
HashMap的 key
二、StringBuilder 与 StringBuffer 的可变性
这两个类都继承自 AbstractStringBuilder,底层是 可扩容的字符数组。
flowchart TB
NEW["StringBuilder sb = new StringBuilder(hello)"] --> INIT["AbstractStringBuilder<br/>char[] value:h,e,l,l,o,...(可扩容)<br/>int count:5(实际长度)<br/>int capacity:16 + 5 = 21(当前容量)"]
INIT --> APPEND["sb.append(world):直接在原数组上追加,不创建新对象"]
APPEND --> AFTER["char[] value:h,e,l,l,o,w,o,r,l,d(同一数组)<br/>int count:11(更新长度)"]
上图展示了可变字符串的工作原理,关键点如下:
- 直接修改内部数组:
append()、insert()、delete()等方法直接操作原数组,不创建新对象。 - 自动扩容:当容量不足时,自动扩容为原来的
2 倍 + 2,并将原数据复制到新数组。 - 预分配容量:如果能预估最终长度,建议构造时指定容量,避免多次扩容:
// 推荐:预估容量,避免扩容 |
三、线程安全性对比
// StringBuilder:线程不安全 |
上图展示了线程安全实现的核心差异:
StringBuilder:所有方法都没有synchronized修饰,多线程并发调用可能导致数据错乱。StringBuffer:几乎所有公共方法都用synchronized修饰,保证同一时刻只有一个线程能操作。String:因为不可变,天然线程安全,无需任何同步措施。
四、性能对比实验
// 测试字符串拼接性能 |
典型运行结果(10 万次拼接):
| 方式 | 耗时 | 说明 |
|---|---|---|
| String | ~5000ms | 创建大量临时对象,频繁 GC |
| StringBuilder | ~5ms | 直接追加,性能最优 |
| StringBuffer | ~8ms | 同步开销约 50% |
五、使用场景指南
// ✅ 场景一:常量、配置项、少量拼接 → 用 String |
面试高频追问
String s = new String("abc")创建了几个对象? 分情况讨论:- 如果字符串常量池中已存在
"abc":创建 1 个 堆对象 - 如果字符串常量池中不存在
"abc":创建 2 个 对象(1 个常量池对象 + 1 个堆对象)
- 如果字符串常量池中已存在
- 为什么
String设计为不可变?- 安全性:防止被恶意修改,适合作为敏感信息存储
- 线程安全:无需同步,可安全共享
- 哈希缓存:
hashCode只需计算一次,提升HashMap性能 - 字符串常量池:相同字符串只存一份,节省内存
String的+拼接和StringBuilder的append()有什么区别?- 编译期常量:
"a" + "b" + "c"会被编译器直接优化为"abc" - 变量拼接:
a + b + c会被编译器自动转换为new StringBuilder().append(a).append(b).append(c).toString() - 循环拼接:循环内用
+每次都会创建新的StringBuilder,性能极差
- 编译期常量:
常见面试变体
- “为什么
String是不可变的?有什么好处?” - “
String s = new String('abc')创建了几个对象?” - “字符串拼接哪种方式性能最好?”
- “
StringBuilder和StringBuffer的区别是什么?”
记忆口诀
可变性:String 不可变,Builder 和 Buffer 都可变 线程安全:Buffer 有锁安全,Builder 无锁快 使用场景:单线程用 Builder,多线程用 Buffer,常量用 String
总结
String 不可变、线程安全但拼接性能差;StringBuilder 可变、单线程性能最优;StringBuffer 可变、多线程安全但略有同步开销。实际开发中,单线程场景优先使用 StringBuilder,循环拼接必须避免使用 String 的 + 操作。
声明:本文转载自小哈的博客 《String、StringBuilder 和 StringBuffer 的区别?》,版权归原文作者所有。转载请保留本声明与原文链接。
评论
匿名评论隐私政策
WalineTwikoo
