StringBuilder 与可变字符串 | JavaSE
StringBuilder 与可变字符串
一、学习目标
完成本章后,你应该能够:
- 解释 String 与 StringBuilder 在“可变性”方面的核心区别。
- 能够创建 StringBuilder 对象,并使用
append()完成字符串拼接。 - 熟练使用
reverse()、length()、toString()等基础 API。 - 理解为什么大量、重复的字符串修改通常适合使用 StringBuilder。
- 理解 StringBuilder 方法链调用的原理。
- 能够根据场景判断应该使用 String 还是 StringBuilder。
- 了解 StringBuilder 的容量机制以及它与 StringBuffer 在线程安全方面的基本区别。
二、核心知识
2.1 为什么已经有 String,还需要 StringBuilder
上一章我们不断强调:
String 是不可变字符串。
例如:
String s = "Java";
s = s + "SE";
最终:
JavaSE
看起来好像:
原来的 Java 被修改成 JavaSE
实际上不能这样理解。
String 本身不可变。
程序得到的是新的字符串结果,再让:
s
引用这个新的结果。
如果只是偶尔拼接几次,这完全没有问题。
例如:
String name = "星雨";
String message = "你好," + name;
这种代码:
- 简单
- 可读性高
- 没有必要为了“性能”机械改成 StringBuilder
真正值得注意的是:
在循环中进行大量、重复、逐步累积的字符串修改。
例如:
String text = "";
for (int i = 0; i < 10000; i++) {
text = text + i;
}
这种代码不断产生新的字符串结果。
此时更适合使用:
StringBuilder
2.2 StringBuilder 是什么
StringBuilder 可以理解为:
一个可变的字符序列容器。
例如:
StringBuilder builder = new StringBuilder();
builder.append("Java");
builder.append("SE");
第一次:
builder
↓
"Java"
第二次:
builder
↓
"JavaSE"
核心区别是:
String
不可变字符串
StringBuilder
可变字符序列
2.3 一个非常形象的理解
String 更像:
打印好的纸条
纸条上写:
Java
想变成:
JavaSE
不是直接把原来的纸条变长,而是得到新的结果。
而 StringBuilder 更像:
白板
一开始写:
Java
然后继续往后写:
SE
白板本身仍然是那个白板,但里面保存的内容可以变化。
所以:
String → 内容稳定
StringBuilder → 内容需要持续修改
2.4 StringBuilder 常用 API
本阶段重点掌握:
| API | 作用 |
| --------------------------- | -------------------- |
| new StringBuilder() | 创建空 StringBuilder |
| new StringBuilder(String) | 根据已有字符串创建 |
| append(...) | 在尾部追加数据 |
| reverse() | 反转字符序列 |
| length() | 获取当前字符数量 |
| toString() | 转换为 String |
此外 StringBuilder 还提供:
insert()
delete()
等真正意义上的可变操作。
但本章核心仍然是:
append
reverse
length
toString
三、使用方法
3.1 创建 StringBuilder
方式一:
StringBuilder builder = new StringBuilder();
创建一个空的 StringBuilder。
当前内容可以理解为:
""
方式二:
StringBuilder builder =
new StringBuilder("Java");
当前内容:
Java
3.2 append():追加内容
最核心的方法:
append()
例如:
StringBuilder builder =
new StringBuilder();
builder.append("Java");
builder.append("SE");
builder.append(21);
System.out.println(builder);
得到:
JavaSE21
append() 有很多重载形式,可以追加:
String
char
int
long
double
boolean
Object
……
例如:
StringBuilder builder =
new StringBuilder();
builder.append("姓名:");
builder.append("张三");
builder.append(",年龄:");
builder.append(20);
builder.append(",Java通过:");
builder.append(true);
System.out.println(builder);
3.3 append() 为什么可以链式调用
经常会看到:
StringBuilder builder =
new StringBuilder();
builder.append("Java")
.append("SE")
.append("-")
.append(21);
为什么可以一直:
.append()
.append()
.append()
因为:
append()
执行完成以后,会返回当前 StringBuilder 对象本身。
可以粗略理解为:
builder.append("Java");
完成以后返回:
builder
于是:
builder
.append("Java")
.append("SE")
.append(21);
实际上就是不断继续操作同一个 StringBuilder。
这种写法叫:
链式调用(Method Chaining)。
3.4 reverse():反转字符串内容
例如:
StringBuilder builder =
new StringBuilder("abc");
builder.reverse();
System.out.println(builder);
结果:
cba
与 String 的很多 API 不同,这里:
builder.reverse();
会直接改变当前 StringBuilder 中保存的字符序列。
3.5 length():获取当前长度
例如:
StringBuilder builder =
new StringBuilder("JavaSE");
System.out.println(builder.length());
得到:
6
追加之后:
builder.append(21);
System.out.println(builder.length());
当前内容:
JavaSE21
长度:
8
3.6 toString():转换回 String
这是 StringBuilder 使用过程中非常重要的一步。
例如:
StringBuilder builder =
new StringBuilder();
builder.append("Java");
builder.append("SE");
String result = builder.toString();
现在:
result
类型就是:
String
为什么需要转换?
因为:
StringBuilder
通常是字符串构建过程中的工具
String
通常才是最终需要保存、传输、返回的数据
典型流程:
StringBuilder
↓
不断 append
↓
完成字符串构建
↓
toString()
↓
String
3.7 一个完整示例
public class StringBuilderDemo {
public static void main(String[] args) {
StringBuilder builder =
new StringBuilder();
builder.append("Java")
.append("SE")
.append('-')
.append(21);
System.out.println(builder);
System.out.println(
builder.length()
);
builder.reverse();
System.out.println(builder);
String result =
builder.toString();
System.out.println(result);
}
}
构建过程:
""
↓ append("Java")
"Java"
↓ append("SE")
"JavaSE"
↓ append('-')
"JavaSE-"
↓ append(21)
"JavaSE-21"
3.8 StringBuilder 拼接案例
假设需要拼接:
1,2,3,4,5
不能简单:
for (int i = 1; i <= 5; i++) {
builder.append(i).append(",");
}
因为最终会得到:
1,2,3,4,5,
最后多了一个逗号。
可以判断:
StringBuilder builder =
new StringBuilder();
for (int i = 1; i <= 5; i++) {
builder.append(i);
if (i < 5) {
builder.append(",");
}
}
System.out.println(builder);
结果:
1,2,3,4,5
这就是 StringBuilder 非常典型的应用:
动态构建复杂字符串。
3.9 StringBuilder 判断回文
例如判断:
level
是否为回文字符串。
回文:
正着读
和
反着读
相同
可以借助:
reverse()
思考:
原字符串
↓
复制到 StringBuilder
↓
reverse()
↓
toString()
↓
与原字符串比较
例如:
String text = "level";
String reversed =
new StringBuilder(text)
.reverse()
.toString();
boolean result =
text.equals(reversed);
System.out.println(result);
3.10 insert():在指定位置插入内容
StringBuilder 还可以真正修改字符序列。
例如:
StringBuilder builder =
new StringBuilder("JavaSE");
builder.insert(4, "-");
System.out.println(builder);
得到:
Java-SE
这里:
insert(4, "-")
表示:
在索引 4 的位置插入
"-"。
3.11 delete():删除一段内容
例如:
StringBuilder builder =
new StringBuilder("Java--SE");
builder.delete(4, 6);
System.out.println(builder);
结果:
JavaSE
delete(start, end) 同样使用:
[start, end)
即:
包前不包后。
这个规则与:
substring()
具有很强的一致性。
四、原理与进阶
4.1 StringBuilder 为什么适合不断追加内容
StringBuilder 内部维护一个:
可扩展的字符存储空间。
可以把它抽象理解成:
StringBuilder
┌──────────────────────────┐
│ J │ a │ v │ a │ │ │
└──────────────────────────┘
继续:
append("SE");
可以在现有字符序列后继续写入:
┌──────────────────────────┐
│ J │ a │ v │ a │ S │ E │
└──────────────────────────┘
当内部空间不足时:
StringBuilder 会自动扩大容量。
因此开发者通常不需要自己管理扩容。
4.2 length 和 capacity 是两回事
例如:
length
表示:
当前已经有多少字符。
而:
capacity
表示:
当前内部空间能够容纳多少字符,超过后需要重新分配更大的内部存储。
默认:
new StringBuilder()
初始容量为:
16
例如:
StringBuilder builder =
new StringBuilder();
System.out.println(builder.length());
System.out.println(builder.capacity());
刚创建时:
length = 0
capacity = 16
因此:
length
≠
capacity
可以类比:
length = 水杯里当前有多少水
capacity = 水杯最多能装多少水
4.3 容量不够怎么办
一般情况下:
StringBuilder
会自动扩大内部容量。
开发者不需要频繁操作它。
如果提前知道最终字符串可能很大,也可以指定初始容量:
StringBuilder builder =
new StringBuilder(1000);
这并不是:
字符串长度直接变成1000
而只是:
提前准备容量
此时:
builder.length()
仍然是:
0
4.4 StringBuilder 和字符串 + 到底怎么选
不要形成错误结论:
看到
+就必须替换成 StringBuilder。
例如:
String message =
"Hello, " + username + "!";
这种代码:
- 简洁
- 清晰
- 可读性很好
完全可以使用。
真正值得显式使用 StringBuilder 的典型情况是:
String result = "";
for (...) {
result = result + 数据;
}
即:
循环中不断构造越来越长的字符串。
推荐:
StringBuilder builder =
new StringBuilder();
for (...) {
builder.append(数据);
}
String result =
builder.toString();
因此应该理解成:
简单拼接
→ 优先可读性
大量重复构建
→ 考虑 StringBuilder
而不是:
String + 一律错误
4.5 不要死记“+ 底层一定变成 StringBuilder”
Java 编译器允许对字符串拼接进行优化。
不同 JDK、不同编译器实现细节可能不同。
因此不应该机械背:
字符串 + 底层一定就是 new StringBuilder().append(...)
这种说法过于绝对。
真正稳定、应该掌握的语义是:
String 本身不可变
以及:
非编译期常量的字符串连接最终产生新的 String 结果
至于 JVM 和编译器具体采用什么优化机制:
属于实现细节。
在现代 JDK 中,这些实现细节可能随着版本演进。
4.6 toString() 之后发生了什么
例如:
StringBuilder builder =
new StringBuilder("Java");
String result =
builder.toString();
builder.append("SE");
System.out.println(result);
System.out.println(builder);
此时:
result
仍然表示转换时得到的:
Java
而:
builder
已经变成:
JavaSE
也就是说:
toString()得到 String 后,后续修改 StringBuilder 不会跟着修改已经得到的 String。
这也符合:
String 不可变
的设计。
4.7 StringBuilder 的线程安全问题
StringBuilder:
不保证同步。
简单来说:
它不是为多个线程同时修改同一个实例而设计的。
这也是 StringBuilder 与:
StringBuffer
的重要区别之一。
StringBuffer 的很多操作具有同步机制。
可以初步理解:
| 类型 | 可变 | 同步保证 | | ------------- | ------ | ---------------------- | | String | 不可变 | 不需要通过可变操作同步 | | StringBuilder | 可变 | 不提供同步保证 | | StringBuffer | 可变 | 提供同步机制 |
在绝大多数普通局部字符串构建场景:
StringBuilder
通常已经足够。
等学习多线程时,再进一步理解:
线程安全
竞态条件
锁
同步
等概念。
五、实践应用
5.1 动态生成文章摘要
假设有多段文字:
String[] words = {
"Java",
"SE",
"是",
"Java",
"技术体系",
"的",
"基础"
};
可以:
StringBuilder builder =
new StringBuilder();
for (String word : words) {
builder.append(word);
}
String result =
builder.toString();
这种:
不断追加
→ 最终生成 String
正是 StringBuilder 的典型工作模型。
5.2 动态构造日志信息
例如:
StringBuilder log =
new StringBuilder();
log.append("userId=")
.append(1001)
.append(", username=")
.append("LingXi")
.append(", action=")
.append("LOGIN");
String result =
log.toString();
最终:
userId=1001, username=LingXi, action=LOGIN
5.3 构造 HTML / SQL / JSON 时需要注意
StringBuilder 从技术上当然可以拼:
HTML
SQL
JSON
例如:
builder.append("<h2>")
.append(title)
.append("</h2>");
但是实际工程中通常还有:
模板引擎
JSON 序列化框架
SQL 参数化
ORM
等更专业方案。
尤其 SQL:
不应该因为会 StringBuilder,就大量手工拼接用户输入 SQL。
因为还会涉及:
SQL 注入
转义
参数绑定
等安全问题。
因此 StringBuilder 是:
字符串构建工具。
不是:
所有文本格式都应该手工拼接。
六、常见问题
6.1 StringBuilder 是 String 的子类吗?
不是。
它们是不同的类。
不能写:
String s =
new StringBuilder();
需要:
StringBuilder builder =
new StringBuilder();
最终转换:
String s =
builder.toString();
6.2 append() 为什么可以追加数字?
例如:
builder.append(100);
因为 StringBuilder 为不同参数类型提供了多种:
append(...)
重载。
数字会以对应字符串形式追加。
因此:
builder.append(100);
最终追加的是字符序列:
100
6.3 append() 为什么能一直点下去?
因为:
append()
返回当前 StringBuilder 对象本身。
因此:
builder.append("A")
.append("B")
.append("C");
可以连续调用。
6.4 StringBuilder 可以直接 println 吗?
可以:
System.out.println(builder);
因为输出过程中会得到它对应的字符串表现。
但是:
如果业务 API 明确要求 String 类型,应该调用
toString()。
例如:
String result =
builder.toString();
6.5 为什么最终通常还要转成 String?
因为 StringBuilder 通常承担:
构建
而 String 承担:
最终字符串值
很多方法、字段、DTO、数据库参数和业务接口最终使用的仍然是:
String
6.6 StringBuilder 是否一定比 String 快?
不能简单这样说。
错误理解:
StringBuilder 永远比 String 快
正确理解:
String 和 StringBuilder 解决的问题不同。
例如:
String name = "Java";
根本没有理由改成:
StringBuilder name =
new StringBuilder("Java");
而:
for (...) {
result += value;
}
这种重复字符串累积才是 StringBuilder 的经典场景。
6.7 StringBuilder 能作为多线程共享对象随便修改吗?
不应该。
StringBuilder 本身:
不保证多线程同步安全。
普通单线程、局部方法内构建字符串:
StringBuilder
通常最合适。
涉及多个线程共享修改时,则必须重新考虑并发安全设计。
6.8 new StringBuilder(100) 是创建了100个字符吗?
不是。
它只是指定:
初始容量 = 100
此时:
builder.length()
仍然是:
0
这是:
容量
与:
实际长度
的区别。
七、练习与验收
7.1 知识问答
- StringBuilder 是什么?
- String 与 StringBuilder 最核心的区别是什么?
- 为什么循环中的大量字符串拼接适合考虑 StringBuilder?
append()有什么作用?- 为什么
append()可以进行链式调用? reverse()是否会改变当前 StringBuilder?toString()的作用是什么?- 为什么最终业务结果通常仍然需要转成 String?
length()与capacity()有什么区别?- StringBuilder 容量不足时会发生什么?
new StringBuilder(100)中的100表示什么?- StringBuilder 是否线程安全?
- StringBuilder 与 StringBuffer 的基本区别是什么?
- 为什么不能简单说“任何字符串 + 都必须换成 StringBuilder”?
- 为什么不应该死记“+ 底层一定使用 StringBuilder”?
7.2 代码阅读
不运行代码,写出执行结果:
public class Test {
public static void main(String[] args) {
StringBuilder builder =
new StringBuilder();
builder.append("Java")
.append("SE")
.append(21);
System.out.println(builder);
System.out.println(builder.length());
builder.reverse();
System.out.println(builder);
}
}
继续阅读:
public class Test {
public static void main(String[] args) {
StringBuilder builder =
new StringBuilder("Java");
String text =
builder.toString();
builder.append("SE");
System.out.println(text);
System.out.println(builder);
}
}
回答:
- 两次输出分别是什么?
- 为什么修改
builder后text没有发生变化? - 这体现了 String 的什么特性?
7.3 手写代码
任务一:高效拼接数字
使用 StringBuilder 构造:
1-2-3-4-5-6-7-8-9-10
要求:
- 最后不能出现多余
- - 最终转换成 String
任务二:反转字符串
输入:
JavaSE
输出:
ESavaJ
要求:
- 使用 StringBuilder
- 不手写逆序循环
任务三:回文判断
判断以下字符串是否为回文:
level
abcba
Java
要求使用:
StringBuilder + reverse + toString + equals
任务四:大量数据构造
使用:
StringBuilder
构建包含:
0 ~ 9999
全部数字的字符串。
思考:
- 为什么这种场景比
String +=更适合 StringBuilder? - 是否有必要提前设置容量?
7.4 Debug
下面代码存在设计问题:
public class DebugDemo {
public static void main(String[] args) {
String result = "";
for (int i = 0; i < 100000; i++) {
result = result + i;
}
System.out.println(result);
}
}
要求:
- 代码语法是否错误?
- 真正的问题是什么?
- 使用 StringBuilder 重构。
- 保证最终结果仍然是 String。
继续 Debug:
StringBuilder builder =
new StringBuilder("Java");
String result = builder;
回答:
- 是否可以编译?
- 为什么?
- 如何正确转换?
7.5 综合训练
设计一个控制台“学生信息报告生成器”。
已有:
姓名
学号
Java成绩
数据结构成绩
数据库成绩
要求生成:
===== 学生成绩报告 =====
姓名:...
学号:...
Java:...
数据结构:...
数据库:...
平均分:...
=======================
要求:
- 使用 StringBuilder 构建报告。
- 使用链式
append()。 - 最终转换为 String。
- 不允许把整个结果直接写成一个固定字符串。
- 思考哪些内容适合动态生成,哪些属于固定模板。
7.6 本章验收
闭卷完成以下内容:
能够口述:
String
vs
StringBuilder
核心区别。
能够手写:
new StringBuilder()
append()
reverse()
length()
toString()
能够解释:
为什么大量重复拼接考虑 StringBuilder
能够解释:
为什么普通的少量 + 并不是错误写法
能够区分:
length
capacity
能够知道:
StringBuilder 不提供线程同步保证
能够完成:
String
→ StringBuilder
→ 修改
→ String
这一完整数据转换流程。