String与StringBuilder 详细总结(附实战案例)
在Java开发中,String和StringBuilder是处理字符串的核心类,前者用于存储不可变字符串,后者用于高效处理可变字符串。本文基于API学习文档,系统梳理两类的核心特性、方法及实战案例,适合作为开发参考或学习笔记。
一、API基础认知
1. 什么是API?
API(Application Programming Interface,应用程序编程接口)是Java官方或第三方提供的“现成类”,开发者可直接调用其方法解决问题,无需重复编写底层逻辑。例如之前学习的Scanner(键盘录入)、Random(生成随机数),以及本文重点的String、StringBuilder均属于API。
2. 学习目标
- 掌握
String类的不可变性、常量池优化及核心方法; - 理解
StringBuilder的可变性、高效性及适用场景; - 能独立完成字符串处理实战案例(登录验证、验证码生成等);
- 区分
String与StringBuilder的内存差异及使用场景。
二、String类:不可变字符串的处理
1. String类的核心特点
String类是Java中最常用的字符串处理类,其核心特性决定了它的使用场景和注意事项:
- 不可变性:
String对象一旦创建,其内容(字符序列)无法修改。任何“修改”操作(如拼接、替换)都会生成新的String对象,原对象不变。 - 线程安全:因不可变性,多线程环境下无需担心并发修改问题,可安全共享
String对象。 - 常量池优化:Java会在“字符串常量池”(元空间中的一块区域)缓存首次创建的字符串。后续使用
String s = "abc"时,会优先从常量池取值,避免重复创建对象,节省内存。 - 支持正则表达式:提供
matches()(匹配正则)、split()(按正则切割)、replace()(按正则替换)等方法,可处理复杂字符串逻辑。 - 可序列化与可比较:实现
Serializable接口(支持网络传输/持久化)、Comparable接口(支持字符串排序)。
2. String类的常见构造方法
| 构造方法 | 说明 |
|---|---|
public String() | 创建空白字符串(长度为0,无任何字符) |
public String(String original) | 根据传入的字符串,创建新的String对象(复用常量池已有对象) |
public String(char[] chs) | 将字符数组转换为字符串(如new String(new char[]{'a','b'})得到"ab") |
public String(byte[] bytes) | 将字节数组转换为字符串(常用于网络传输或文件读取后的解码) |
3. String类的内存存储(面试重点)
String的创建方式不同,内存存储逻辑也不同,这是面试高频考点,需重点理解:
场景1:直接赋值(String s1 = "abc")
- 执行逻辑:JVM先检查字符串常量池,若已存在
"abc",则s1直接指向常量池中的对象;若不存在,先在常量池创建"abc",再让s1指向它。 示例代码:
String s1 = "abc"; String s2 = "abc"; System.out.println(s1 == s2); // true(s1和s2指向常量池同一对象)
场景2:new关键字创建(String s2 = new String("abc"))
- 执行逻辑:无论常量池是否存在
"abc",都会在堆内存创建新的String对象,该对象的value属性指向常量池中的"abc";最终s2指向堆内存的新对象。 示例代码:
String s1 = "abc"; String s2 = new String("abc"); System.out.println(s1 == s2); // false(s1指向常量池,s2指向堆内存)
场景3:变量拼接(String s3 = s2 + "c")
- 执行逻辑:JDK8及以下会创建
StringBuilder对象,通过append()拼接后调用toString()生成新的String对象(存入堆内存,不进入常量池);因此s3与常量池中的"abc"地址不同。 示例代码:
String s1 = "abc"; String s2 = "ab"; String s3 = s2 + "c"; System.out.println(s1 == s3); // false(s3指向堆内存新对象)
场景4:常量拼接(String s2 = "a" + "b" + "c")
- 执行逻辑:编译期会直接优化为
"abc",相当于String s2 = "abc",因此s2指向常量池中的"abc",与s1地址相同。 示例代码:
String s1 = "abc"; String s2 = "a" + "b" + "c"; System.out.println(s1 == s2); // true(编译期优化为同一常量)
4. String类的核心方法
String类提供了丰富的方法用于字符串处理,以下是开发中最常用的类别:
(1)字符串比较
public boolean equals(String str):严格比较两个字符串的内容(大小写、字符序列完全一致才返回true),区分==(比较地址)。public boolean equalsIgnoreCase(String str):忽略大小写比较内容(如"Abc".equalsIgnoreCase("aBC")返回true)。
(2)字符串遍历
public char[] toCharArray():将字符串转换为字符数组,便于遍历每个字符。public char charAt(int index):根据索引(从0开始)获取指定位置的字符(若索引越界,抛StringIndexOutOfBoundsException)。public int length():返回字符串的长度(字符个数),注意与数组length属性(无括号)区分。
(3)字符串截取
public String substring(int beginIndex):从指定索引开始,截取到字符串末尾(如"abcde".substring(2)返回"cde")。public String substring(int beginIndex, int endIndex):截取[beginIndex, endIndex)范围的字符(包头不包尾,如"abcde".substring(1,3)返回"bc")。
(4)字符串替换与切割
public String replace(CharSequence oldStr, CharSequence newStr):将字符串中的oldStr全部替换为newStr,返回新字符串(原字符串不变)。public String[] split(String regex):根据regex(正则表达式)切割字符串,返回切割后的字符串数组(如"a,b,c".split(",")返回["a","b","c"])。
5. String类实战案例(附代码与思路)
案例1:用户登录(三次机会)
题目需求
已知正确的用户名("itheima")和密码("123456"),模拟用户登录:键盘录入用户名和密码,共给三次机会,登录成功/失败给出对应提示。
解题思路
- 定义正确的用户名和密码(常量);
- 使用
for循环控制三次机会(循环变量从3递减到1); - 每次循环中,用
Scanner录入用户输入的用户名和密码; - 用
equals()比较输入值与正确值,匹配则提示成功并退出循环,不匹配则提示剩余次数; - 三次均失败,提示“登录失败”。
核心代码
import java.util.Scanner;
public class LoginDemo {
public static void main(String[] args) {
// 1. 定义正确的用户名和密码
String correctUsername = "itheima";
String correctPassword = "123456";
Scanner sc = new Scanner(System.in);
// 2. 循环控制三次机会
for (int i = 3; i >= 1; i--) {
System.out.print("请输入用户名:");
String inputUsername = sc.next();
System.out.print("请输入密码:");
String inputPassword = sc.next();
// 3. 比较输入值与正确值
if (correctUsername.equals(inputUsername) && correctPassword.equals(inputPassword)) {
System.out.println("登录成功!");
break; // 登录成功,退出循环
} else {
if (i > 1) {
System.out.println("用户名或密码错误,剩余" + (i - 1) + "次机会");
} else {
System.out.println("三次机会已用完,登录失败!");
}
}
}
sc.close();
}
}代码解释
correctUsername.equals(inputUsername):严格比较用户名内容,避免==比较地址的错误;- 循环变量
i从3递减,每次失败后提示剩余次数,逻辑更清晰; - 三次失败后不进入
break,直接执行循环外逻辑,提示最终结果。
案例2:统计字符次数
题目需求
键盘录入一个字符串,统计其中大写字母、小写字母、数字字符的出现次数(不考虑其他字符)。例如:输入"aAb3&c2B*4CD1",输出“小写字母:3个,大写字母:4个,数字:4个”。
解题思路
- 初始化三个计数器(
upperCount、lowerCount、numberCount),初始值为0; - 用
toCharArray()将字符串转换为字符数组,遍历每个字符; 对每个字符进行判断:
- 大写字母:
char >= 'A' && char <= 'Z',upperCount++; - 小写字母:
char >= 'a' && char <= 'z',lowerCount++; - 数字:
char >= '0' && char <= '9',numberCount++;
- 大写字母:
- 遍历结束后,打印三个计数器的值。
核心代码
import java.util.Scanner;
public class CharCountDemo {
public static void main(String[] args) {
// 1. 初始化计数器
int upperCount = 0; // 大写字母计数
int lowerCount = 0; // 小写字母计数
int numberCount = 0; // 数字计数
Scanner sc = new Scanner(System.in);
System.out.print("请输入字符串:");
String str = sc.next();
// 2. 转换为字符数组并遍历
char[] chars = str.toCharArray();
for (char c : chars) {
if (c >= 'A' && c <= 'Z') {
upperCount++;
} else if (c >= 'a' && c <= 'z') {
lowerCount++;
} else if (c >= '0' && c <= '9') {
numberCount++;
}
// 其他字符不处理
}
// 3. 打印结果
System.out.println("小写字母:" + lowerCount + "个");
System.out.println("大写字母:" + upperCount + "个");
System.out.println("数字:" + numberCount + "个");
sc.close();
}
}代码解释
str.toCharArray():将字符串拆分为字符数组,便于逐个判断;- 字符比较依赖ASCII码:
'A'-'Z'对应ASCII值65-90,'a'-'z'对应97-122,'0'-'9'对应48-57; - 多分支
if-else确保每个字符只被统计一次,逻辑严谨。
案例3:手机号屏蔽
题目需求
以字符串形式从键盘接受一个手机号(11位),将中间四位号码屏蔽,最终效果如:"15616501234"→"156****1234"。
解题思路
- 键盘录入手机号(需确保是11位,此处简化处理);
- 用
substring()截取手机号的前3位(0-3,包头不包尾)和后4位(7-11); - 将前3位、
"****"、后4位拼接,得到屏蔽后的手机号; - 打印结果。
核心代码
import java.util.Scanner;
public class PhoneMaskDemo {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
System.out.print("请输入11位手机号:");
String phone = sc.next();
// 1. 截取前3位(0-3)和后4位(7-11)
String prefix = phone.substring(0, 3); // 前3位:索引0到2
String suffix = phone.substring(7); // 从索引7到末尾(后4位)
// 2. 拼接屏蔽后的字符串
String maskedPhone = prefix + "****" + suffix;
// 3. 打印结果
System.out.println("屏蔽后手机号:" + maskedPhone);
sc.close();
}
}代码解释
phone.substring(0,3):截取索引0、1、2的字符(前3位),符合[begin, end)规则;phone.substring(7):从索引7开始截取到末尾,11位手机号的索引7-10对应后4位;- 拼接时直接用
+,因仅一次拼接,String的不可变性对性能影响可忽略。
三、StringBuilder类:高效处理可变字符串
1. 为什么需要StringBuilder?
String的不可变性导致“频繁修改字符串”(如循环拼接)时,会创建大量临时对象,浪费内存且效率低。StringBuilder通过可变字符数组实现,修改直接作用于数组,避免频繁创建对象,是处理可变字符串的核心工具。
2. StringBuilder的核心特点
- 可变性:底层存储为
char[]数组,添加、删除、替换字符时直接修改数组,不创建新对象(扩容除外)。 - 非线程安全:方法未加
synchronized同步锁,多线程环境下可能出现数据不一致,线程安全场景需用StringBuffer(性能略低)。 高效性:
- 避免频繁创建临时对象;
- 扩容机制:当字符长度超出当前容量时,自动扩容为“当前容量×2 + 2”(默认初始容量16),减少扩容次数。
初始容量:
- 无参构造:默认16个字符(
new StringBuilder()); - 指定容量:
new StringBuilder(100)(初始容量100,适合已知大致长度的场景); - 基于字符串:
new StringBuilder("Hello")(初始容量=字符串长度+16=5+16=21)。
- 无参构造:默认16个字符(
3. StringBuilder的构造方法
| 构造方法 | 说明 |
|---|---|
public StringBuilder() | 无参构造,初始容量16 |
public StringBuilder(int capacity) | 指定初始容量,避免频繁扩容 |
public StringBuilder(String str) | 基于字符串初始化,初始容量=字符串长度+16 |
4. StringBuilder的核心方法
StringBuilder的方法支持链式调用(方法返回this),使用更简洁,核心方法如下:
| 方法 | 作用 | 示例 |
|---|---|---|
append(...) | 末尾追加任意类型数据(String、int、char等) | sb.append("Hello").append(123)(结果"Hello123") |
insert(int offset, ...) | 在指定索引offset处插入数据 | sb.insert(5, "World")("Hello"→"HelloWorld") |
replace(int start, int end, String str) | 替换[start, end)范围的字符为str | sb.replace(0,5,"Hi")("HelloWorld"→"HiWorld") |
delete(int start, int end) | 删除[start, end)范围的字符 | sb.delete(2,7)("HiWorld"→"Hi") |
deleteCharAt(int index) | 删除指定索引index处的字符 | sb.deleteCharAt(1)("Hi"→"H") |
reverse() | 反转字符序列 | sb.append("abc").reverse()(结果"cba") |
charAt(int index) | 获取指定索引的字符 | sb.charAt(1)("abc"→'b') |
setCharAt(int index, char c) | 修改指定索引的字符为c | sb.setCharAt(1,'x')("abc"→"axc") |
length() | 返回当前字符个数(实际存储的字符数) | sb.append("abc").length()(结果3) |
capacity() | 返回当前容量(数组的长度) | new StringBuilder().capacity()(结果16) |
5. StringBuilder实战案例(附代码与思路)
案例1:对称字符串判断
题目需求
键盘接受一个字符串,判断该字符串是否为对称字符串(正读和反读一致,如"123321"、"111"是对称字符串,"123123"不是),并打印“是”或“不是”。
解题思路
- 键盘录入字符串;
- 创建
StringBuilder对象,传入该字符串并调用reverse()方法反转; - 将反转后的
StringBuilder转换为String,与原字符串用equals()比较; - 若相等则为对称字符串,否则不是。
核心代码
import java.util.Scanner;
public class SymmetricStringDemo {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
System.out.print("请输入字符串:");
String str = sc.next();
// 1. 反转字符串(用StringBuilder)
StringBuilder sb = new StringBuilder(str);
String reversedStr = sb.reverse().toString(); // 反转后转为String
// 2. 比较原字符串与反转后的字符串
if (str.equals(reversedStr)) {
System.out.println("是对称字符串");
} else {
System.out.println("不是对称字符串");
}
sc.close();
}
}代码解释
sb.reverse():直接反转StringBuilder中的字符序列,无需手动遍历交换;sb.toString():将StringBuilder转换为String,才能与原字符串用equals()比较;- 该方法逻辑简洁,避免了手动遍历字符串首尾字符的繁琐操作。
案例2:数组拼接字符串
题目需求
定义一个方法,将int数组中的数据按照指定格式(如[1, 2, 3, 4, 5])拼接成一个字符串返回;调用该方法并在控制台输出结果(示例:数组{1,2,3,4,5}→输出"[1, 2, 3, 4, 5]")。
解题思路
- 定义方法
arrayToString(int[] arr),返回值为String; - 在方法内创建
StringBuilder对象,先追加"["; 遍历数组,对每个元素:
- 若不是最后一个元素:追加元素 +
", "; - 若是最后一个元素:仅追加元素;
- 若不是最后一个元素:追加元素 +
- 遍历结束后,追加
"]",将StringBuilder转换为String并返回; - 在
main方法中创建数组,调用方法并打印结果。
核心代码
public class ArrayToStringDemo {
public static void main(String[] args) {
// 1. 定义int数组
int[] arr = {1, 2, 3, 4, 5};
// 2. 调用方法拼接字符串
String result = arrayToString(arr);
// 3. 打印结果
System.out.println(result); // 输出:[1, 2, 3, 4, 5]
}
// 定义方法:将int数组拼接为指定格式的字符串
public static String arrayToString(int[] arr) {
// 处理数组为null的情况(可选)
if (arr == null) {
return "";
}
// 1. 创建StringBuilder对象
StringBuilder sb = new StringBuilder();
sb.append("["); // 先追加左括号
// 2. 遍历数组
for (int i = 0; i < arr.length; i++) {
if (i == arr.length - 1) {
// 最后一个元素:仅追加元素
sb.append(arr[i]);
} else {
// 非最后一个元素:追加元素 + ", "
sb.append(arr[i]).append(", ");
}
}
// 3. 追加右括号
sb.append("]");
// 4. 转换为String并返回
return sb.toString();
}
}代码解释
- 用
StringBuilder的append()链式调用,避免String拼接产生临时对象,效率更高; - 遍历中通过
i == arr.length - 1判断是否为最后一个元素,避免末尾多一个", "; - 增加
arr == null的判断,提升方法的健壮性(避免空指针异常)。
四、String与StringBuilder的对比与选择
1. 内存差异(核心区别)
| 场景 | String(+拼接) | StringBuilder(append()) |
|---|---|---|
| 内存开销 | 每次+创建StringBuilder和String对象(如"a"+"b"+"c"创建2个StringBuilder、2个String) | 仅创建1个StringBuilder对象,直接修改底层数组 |
| 效率 | 低(频繁创建对象,GC回收开销大) | 高(无临时对象,直接操作数组) |
2. 适用场景选择
使用String:
- 字符串内容无需修改(如存储常量、配置信息);
- 仅需少量拼接(如
"Hello" + "World",编译期优化后无性能问题); - 多线程环境下共享字符串(不可变性保证线程安全)。
使用StringBuilder:
- 频繁修改字符串(如循环拼接、插入、删除、反转);
- 单线程环境(非线程安全);
- 已知字符串大致长度,可指定初始容量减少扩容(如
new StringBuilder(100))。
五、总结
String是不可变字符串类,依赖常量池优化,适合存储静态字符串;StringBuilder是可变字符串类,底层基于char[]数组,适合频繁修改字符串;- 核心方法需熟练掌握(
String的equals()、substring()、split();StringBuilder的append()、reverse()); - 实战中需根据“是否修改字符串”选择合适的类,兼顾性能与安全性。
本文覆盖了String与StringBuilder的核心知识点及实战案例,可作为日常开发的参考手册,也可用于面试前的复习梳理。