哈喽,各位Java小伙伴们👋!上回咱们吃透了包装类,今天来盘一盘Java里“出镜率天花板”的字符串家族——String、StringBuffer、StringBuilder。

这仨经常被新手搞混:为啥有的字符串改不了?为啥有的改得快但不安全?今天用“管家”的比喻,把这仨讲得明明白白,再也不踩坑!

一、大哥String:不可变的“死板管家”

String是咱们最开始接触的字符串类,核心特点就一个:一旦创建,内容永不改变!就像刻在石头上的字,想改?其实是重新刻一块新石头~

1.1 String的核心特点(记牢!面试必问)

先上“硬核知识点”,再用大白话翻译:

  • ✨ String是final类:不能被继承,相当于这个管家“祖传配方,绝不改版”;

  • ✨ 底层用final char[] value存数据:数组本身和数组内容都改不了,这是“不可变”的根本原因;

  • ✨ 字符串字面量存在常量池(方法区):相同内容的字符串共享一个对象,节省内存。

💡 趣味举例:

String s = "Java"; s = s + "EE"; // 看似修改了s,实则创建了新字符串"JavaEE",原"Java"还在常量池里!

1.2 构造方法:字面量vs new,天差地别!

创建String有两种方式,内存布局完全不同,这是高频坑点,用“内存图解+代码”讲清楚👇

(1)字面量方式:省钱的“共享模式”
String s1 = "javaEE"; String s2 = "javaEE"; // s1和s2指向常量池里同一个char[],堆里甚至只创建1个String对象! System.out.println(s1 == s2); // true(地址相同)

内存示意图(文字版):

【栈】s1 → 地址A 【栈】s2 → 地址A ↓ 【堆】String对象(地址A)→ value数组地址 → 【常量池】char[](存javaEE)
(2)new构造方法:费钱的“独享模式”
String s3 = new String("javaEE"); String s4 = new String("javaEE"); // s3和s4是堆里两个不同的String对象,只是value指向常量池同一个char[] System.out.println(s3 == s4); // false(地址不同) System.out.println(s3.equals(s4)); // true(内容相同)

内存示意图(文字版):

【栈】s3 → 地址A 【栈】s4 → 地址B ↓ ↓ 【堆】String对象A 【堆】String对象B ↓ ↓ 【常量池】char[](存javaEE)← 共用同一个数组

⚠️ 坑点提醒:new String("javaEE") 会创建至少2个对象(常量池的char[] + 堆的String对象),非必要别这么写!

1.3 String的“十八般武艺”:常用方法(附示例)

String的方法贼多,挑最常用的10个,代码可直接复制运行👇

public class StringDemo { public static void main(String[] args) { String str = " Hello JavaEE! "; // 1. charAt(int index):取指定索引的字符(索引从0开始) char c = str.charAt(3); // 结果:'l'(跳过前面两个空格) // 2. length():获取长度(注意:数组是length属性,字符串是length()方法!) int len = str.length(); // 结果:14(包含前后空格) // 3. trim():去掉首尾空格 String trimStr = str.trim(); // 结果:"Hello JavaEE!" // 4. toUpperCase()/toLowerCase():大小写转换 String upperStr = trimStr.toUpperCase(); // 结果:"HELLO JAVAEE!" // 5. indexOf(String str):找子串的索引(找不到返回-1) int index = trimStr.indexOf("Java"); // 结果:6 // 6. substring(int begin, int end):截取(左闭右开!) String subStr = trimStr.substring(6, 10); // 结果:"Java"(取6、7、8、9位) // 7. replaceAll(String regex, String replacement):替换 String replaceStr = trimStr.replaceAll("JavaEE", "Python"); // 结果:"Hello Python!" // 8. split(String regex):分割(注意转义,比如"."要写"\\.") String[] splitArr = trimStr.split(" "); // 按空格分割,结果:["Hello", "JavaEE!"] // 9. toCharArray():转char数组 char[] charArr = trimStr.toCharArray(); // 结果:['H','e','l','l','o',' ','J','a','v','a','E','E','!'] // 10. equals(Object obj):比较内容(千万别用==!) boolean isEqual = trimStr.equals("Hello JavaEE!"); // 结果:true } }

💡 小技巧:substring是“左闭右开”,比如substring(0,3)取0、1、2位,记不住就想“取头不取尾”~

二、二哥StringBuffer:线程安全的“稳重管家”

既然String改不了,那频繁拼接字符串(比如循环里拼)岂不是要创建一堆对象?这时候就需要“可变字符串”——StringBuffer登场!

2.1 核心特点

  • ✨ 可变字符序列:内容可以修改,不用频繁创建新对象;

  • ✨ 线程安全:方法加了synchronized锁,多线程下用着放心;

  • ✨ 初始容量:默认16个字符,满了会自动扩容(扩容会耗性能,能预估长度就指定!)。

2.2 构造方法

// 1. 默认构造:初始容量16 StringBuffer sb1 = new StringBuffer(); // 2. 指定初始容量:避免扩容,性能更好 StringBuffer sb2 = new StringBuffer(100); // 3. 用字符串初始化:容量=字符串长度+16 StringBuffer sb3 = new StringBuffer("Java"); // 容量=4+16=20

2.3 核心方法(最常用就3个!)

StringBuffer sb = new StringBuffer("Hello"); // 1. append():追加字符串(核心!) sb.append(" Java"); // 结果:"Hello Java" sb.append(123); // 支持追加数字,结果:"Hello Java123" // 2. reverse():字符串反转 sb.reverse(); // 结果:"321avaJ olleH" // 3. toString():转成String(最终要返回String就用这个) String result = sb.toString();

三、三弟StringBuilder:追求速度的“暴躁管家”

StringBuilder和StringBuffer长得几乎一模一样,核心区别就一个:线程不安全,但速度更快

3.1 核心特点

  • ✨ 可变字符序列:和StringBuffer一样;

  • ✨ 线程不安全:方法没加锁,单线程下速度比StringBuffer快30%+;

  • ✨ 应用场景:单线程、频繁修改字符串(日常开发90%的场景用它!)。

3.2 用法(和StringBuffer完全一致)

StringBuilder sb = new StringBuilder("Hello"); sb.append(" Python"); // 追加 sb.reverse(); // 反转 String result = sb.toString(); // 转String

四、三剑客终极对比:什么时候用谁?(收藏版)

用表格总结,一目了然👇

类名

可变性

线程安全

执行速度

适用场景

趣味标签

String

不可变

天生安全(无修改操作)

一般

字符串少修改、固定值(比如常量、配置)

死板管家

StringBuffer

可变

安全(加锁)

较慢

多线程环境(比如电商订单、并发日志)

稳重管家

StringBuilder

可变

不安全(无锁)

最快

单线程环境(比如循环拼接、字符串处理)

暴躁管家

💡 面试高频题:为什么不推荐在循环里用String拼接?

答:每次拼接都会创建新String对象,常量池和堆里一堆垃圾对象,性能差;改用StringBuilder,全程只创建1个对象,速度起飞!

五、趣味小测试(自测一下)

  1. String s1 = "abc"; String s2 = "a"+"b"+"c"; → s1 == s2?(答案:true,编译期优化成"abc")

  2. StringBuffer sb = new StringBuffer("abc"); sb.append("d"); → sb == sb.toString()?(答案:false,一个是StringBuffer对象,一个是String对象)

  3. 单线程循环拼接10万次字符串,用谁最快?(答案:StringBuilder)


总结

  1. String不可变,字面量存在常量池,new String会创建多对象,比较内容用equals而非==

  2. StringBuffer线程安全但速度慢,StringBuilder线程不安全但速度快,二者均为可变字符串;

  3. 单线程频繁修改字符串用StringBuilder,多线程用StringBuffer,字符串固定不变用String。

如果这篇文章帮你分清了字符串三剑客,记得点赞+收藏🌟!有踩过字符串的坑?评论区聊聊~关注我,持续更新Java基础干货~

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐