StringBuilder与StringBuffer

为什么需要StringBuilder和StringBuffer

因为 String类与字符串操作 里说了,String是不可变的,每次修改都会创建新对象

打个比方:String就像用石头刻字,改一个字就要换一块新石头;StringBuilder就像用白板写字,随便擦随便改,始终是同一块板子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Test
public void testWhyNeedStringBuilder() {
// 用String拼接:每次 + 都创建新对象
// "a" → "ab" → "abc" → "abcd" → "abcde"
// 中间的 "ab" "abc" "abcd" 全都变成垃圾了,浪费!
String s = "";
for (int i = 0; i < 5; i++) {
s = s + (char) ('a' + i);
}

// 用StringBuilder:始终在同一个对象上操作
// 内部维护一个可变的char数组,append就是往数组后面加字符
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 5; i++) {
sb.append((char) ('a' + i));
}
System.out.println(sb.toString()); // "abcde"
}

StringBuilder:单线程首选

线程不安全但快,日常开发90%的场景用它就对了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@Test
public void testStringBuilder() {
// 创建方式
StringBuilder sb1 = new StringBuilder(); // 空的,默认容量16
StringBuilder sb2 = new StringBuilder("hello"); // 带初始内容
StringBuilder sb3 = new StringBuilder(100); // 指定初始容量(预估内容较长时用)

// append:追加内容(最常用)
sb1.append("hello");
sb1.append(" ");
sb1.append("world");
sb1.append(123); // 可以追加任意类型,自动转成字符串
System.out.println(sb1); // "hello world123"

// 链式调用(append返回自身,可以连着写)
StringBuilder sb = new StringBuilder();
sb.append("姓名:").append("张三").append(",年龄:").append(18);
System.out.println(sb); // "姓名:张三,年龄:18"
}

StringBuffer:多线程用它

线程安全但慢,方法上加了 synchronized 关键字

用法和StringBuilder一模一样,只是多线程环境才需要用

1
2
3
4
5
6
7
8
9
10
11
12
@Test
public void testStringBuffer() {
// 用法和StringBuilder完全一样
StringBuffer sbf = new StringBuffer();
sbf.append("多线程");
sbf.append("安全");
System.out.println(sbf.toString()); // "多线程安全"

// 什么时候用StringBuffer?
// 多个线程同时操作同一个字符串拼接对象时(很少见)
// 绝大多数情况用StringBuilder就行
}

常用方法详解

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
@Test
public void testCommonMethods() {
StringBuilder sb = new StringBuilder("hello world");

// append:在末尾追加
sb.append("!");
System.out.println(sb); // "hello world!"

// insert:在指定位置插入
sb.insert(5, ",");
System.out.println(sb); // "hello, world!"

// delete:删除指定范围(含头不含尾)
sb.delete(5, 7);
System.out.println(sb); // "helloworld!"

// replace:替换指定范围的内容
sb.replace(5, 10, " Java");
System.out.println(sb); // "hello Java!"

// reverse:反转
sb.reverse();
System.out.println(sb); // "!avaJ olleh"

// toString:转成String
String result = sb.toString();
System.out.println(result);

// length 和 charAt 也有
sb = new StringBuilder("abc");
System.out.println(sb.length()); // 3
System.out.println(sb.charAt(1)); // 'b'
}
方法 作用 示例 结果
append(x) 末尾追加 sb.append("!") “hello!”
insert(i, x) 指定位置插入 sb.insert(0, "hi") “hihello”
delete(a, b) 删除[a,b) sb.delete(0, 2) “llo”
replace(a, b, s) 替换[a,b) sb.replace(0, 5, "hi") “hi”
reverse() 反转 sb.reverse() “olleh”
toString() 转成String sb.toString() “hello”

String vs StringBuilder vs StringBuffer 对比

特性 String StringBuilder StringBuffer
可变性 不可变 可变 可变
线程安全 安全(不可变天生安全) 不安全 安全(synchronized)
性能 拼接最慢 最快 比StringBuilder稍慢
使用场景 字符串不怎么变化 单线程拼接(首选) 多线程拼接(少见)
出现版本 JDK 1.0 JDK 1.5 JDK 1.0

选择口诀

字符串基本不变 → String

单线程频繁拼接 → StringBuilder(99%的情况)

多线程频繁拼接 → StringBuffer(很少遇到)

实战:循环拼接字符串的正确写法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
@Test
public void testLoopConcat() {
// 场景1:拼接SQL的IN条件
int[] ids = {1, 3, 5, 7, 9};
StringBuilder sql = new StringBuilder("SELECT * FROM users WHERE id IN (");
for (int i = 0; i < ids.length; i++) {
if (i > 0) {
sql.append(", ");
}
sql.append(ids[i]);
}
sql.append(")");
System.out.println(sql);
// SELECT * FROM users WHERE id IN (1, 3, 5, 7, 9)

// 场景2:拼接CSV格式
String[] names = {"张三", "李四", "王五"};
StringBuilder csv = new StringBuilder();
for (int i = 0; i < names.length; i++) {
if (i > 0) {
csv.append(",");
}
csv.append(names[i]);
}
System.out.println(csv); // "张三,李四,王五"

// 场景3:也可以用 String.join(Java 8+,更简洁)
String joined = String.join(",", names);
System.out.println(joined); // "张三,李四,王五"
}

小技巧:如果你知道最终字符串大概多长,可以在构造时指定容量,避免内部数组扩容

1
2
// 预估大约200个字符,直接指定容量
StringBuilder sb = new StringBuilder(200);

常见坑

说明
StringBuilder不能用 ==equals 比较内容 StringBuilder没有重写equals,比较内容要先 toString() 再比
循环里每次new StringBuilder 应该在循环外面创建,循环里只append
忘了调 toString() StringBuilder传给需要String的方法时要转换
多线程用StringBuilder 数据可能错乱,多线程请用StringBuffer或加锁

练习题

题1:用StringBuilder实现字符串反转

1
2
3
4
5
6
@Test
public void testReverseWithSB() {
String input = "Hello World";
String reversed = new StringBuilder(input).reverse().toString();
System.out.println(reversed); // "dlroW olleH"
}

题2:把数组转成 “[1, 2, 3, 4, 5]” 格式的字符串

1
2
3
4
5
6
7
8
9
10
11
@Test
public void testArrayToString() {
int[] arr = {1, 2, 3, 4, 5};
StringBuilder sb = new StringBuilder("[");
for (int i = 0; i < arr.length; i++) {
if (i > 0) sb.append(", ");
sb.append(arr[i]);
}
sb.append("]");
System.out.println(sb); // [1, 2, 3, 4, 5]
}

题3:统计StringBuilder的扩容过程

1
2
3
4
5
6
7
8
9
@Test
public void testCapacity() {
StringBuilder sb = new StringBuilder(); // 默认容量16
System.out.println("初始容量:" + sb.capacity()); // 16

sb.append("12345678901234567"); // 17个字符,超过16
System.out.println("扩容后容量:" + sb.capacity()); // 34(16*2+2)
// 扩容规则:新容量 = 旧容量 * 2 + 2(大致规则,实际有细微差异)
}

上一章 目录 下一章
String类与字符串操作 java基础 包装类与自动装拆箱