【Java常用API】----- 字符串 与 链式编程
1. String
1.1 String 概述
String是 Java 定义好的一个类,位于java.lang包中,使用时无需手动导入。- Java 程序中的所有字符串文字(如
"abcdefg")都是String类的对象。 String对象是不可变的,一旦创建,其内容不能被修改。
示例:
String name = "尼古拉斯·阿玮";创建一个字符串对象;name = "三连加投币·阿玮";并非修改原对象,而是将name引用指向新的字符串对象;- 因此,每次赋值都可能产生新对象,旧对象若无引用则可被垃圾回收。
注意:
字符串拼接、替换等操作看似“修改”,实则返回新字符串,原字符串保持不变
1.2 String 构造方法
1.2.1 代码格式
创建 String 对象主要有两种方式:
-
直接赋值:
String s1 = "abc"; -
使用
new关键字调用构造方法:String s2 = new String("abc");
常用构造方法如下:
| 构造方法 | 说明 |
|---|---|
public String() | 创建一个空字符串对象 |
public String(String original) | 根据传入的字符串内容创建新对象 |
public String(char[] chs) | 根据字符数组创建字符串对象 |
public String(byte[] chs) | 根据字节数组创建字符串对象(默认 UTF-8 编码) |
1.2.2 内存分析

1.2.2.1 直接赋值(字符串常量池机制)
public class demo6 {public static void main(String[] args) {String s1 = "abc";String s2 = "abc";}
}

- JVM 会检查字符串常量池(StringTable)中是否已存在
"abc";- 若存在,则复用该对象,
s1和s2指向同一地址;- 若不存在,则在常量池中创建新对象。
1.2.2.2 使用 new 创建对象
public class demo6 {public static void main(String[] args) {char[] chs = {'a', 'b', 'c'};String s1 = new String(chs);String s2 = new String(chs);}
}

- 每次调用
new String(...)都会在堆内存中创建一个新的String对象;- 即使内容相同,也会生成两个独立对象;
- 但内部字符数组共享(若未复制);
1.3 String 之间的比较
1.3.1 == 比较的本质
在 Java 中,== 运算符的比较行为取决于操作数的数据类型:

关键:
==对于字符串来说,比较的是“地址是否相同”,而不是“内容是否相等”。
1.3.2 字符串比较
由于 == 只能判断引用是否相同,不能直接比较字符串内容,因此 Java 提供了专门的方法来比较字符串内容。
| 方法 | 说明 |
|---|---|
boolean equals(Object obj) | 比较两个字符串的内容是否完全一致(大小写敏感) |
boolean equalsIgnoreCase(String str) | 忽略大小写比较两个字符串的内容 |
1.3.3 示例分析
// 示例1:使用字面量创建字符串
String s1 = "abc";
String s2 = "abc";
System.out.println(s1 == s2); // true —— 同一个常量池对象,地址相同
System.out.println(s1.equals(s2)); // true —— 内容相同
// 示例2:使用 new 创建字符串
String s3 = new String("abc");
String s4 = "abc";
System.out.println(s3 == s4); // false —— 不同对象,地址不同
System.out.println(s3.equals(s4)); // true —— 内容相同
// 示例3:用户输入 vs 字面量
Scanner sc = new Scanner(System.in);
String str1 = sc.next(); // 输入 "abc"
String str2 = "abc";
System.out.println(str1 == str2); // false —— 不同对象
System.out.println(str1.equals(str2)); // true —— 内容相同
为什么示例3中 str1 == str2 返回 false:
str1是通过Scanner读取的字符串,存储在堆中,是一个新对象;str2是字面量,JVM 会在字符串常量池中查找或创建"abc";- 虽然内容相同,但它们是两个不同的对象,内存地址不同;
- 所以
==返回false。
1.3.4 正确的比较做法
使用 equals() 或 equalsIgnoreCase()
// 安全比较字符串内容
if (str1.equals(str2)) {System.out.println("内容相同!");
}// 忽略大小写比较
if (str1.equalsIgnoreCase("ABC")) {System.out.println("忽略大小写后相同!");
}
注意:避免使用
==比较字符串内容,除非你明确想判断是否为同一个对象。
1.4 String基本操作
1.4.1 遍历
字符串可以通过 charAt(int index) 方法逐个访问字符,实现遍历。
常用方法:
public char charAt(int index):返回指定索引位置的字符(从 0 开始)。public int length():返回字符串长度。
注意:
- 字符串长度通过
.length()获取,而数组长度是.length(无括号);- 索引越界会抛出
StringIndexOutOfBoundsException。
示例:
import java.util.Scanner;public class demo1 {public static void main(String[] args) {Scanner sc = new Scanner(System.in);System.out.println("请输入一个字符串:");String str = sc.next();for (int i = 0; i < str.length(); i++) {char c = str.charAt(i);System.out.println(c);}}
}
输出效果:
1.4.2 拼接
字符串拼接是常见需求,Java 提供多种方式实现。
常见方式:
- 使用
+运算符;- 使用
StringBuilder(高效,适用于大量拼接)。
示例:将整型数组按格式拼接为字符串 [1, 2, 3]
public class demo2 {public static void main(String[] args) {int[] arr = {1, 2, 3, 4, 5};System.out.println(subStr(arr));}public static String subStr(int[] arr) {if (arr.length == 0) {return "[]";}String str = "[";for (int i = 0; i < arr.length; i++) {str += arr[i];if (i < arr.length - 1) {str += ",";}}str += "]";return str;}
}
输出结果:
注意:频繁使用
+拼接字符串效率较低,建议在循环中使用StringBuilder。
1.4.3 截取
substring() 是字符串截取的核心方法。
方法说明:
| 方法 | 说明 |
|---|---|
substring(int beginIndex) | 从指定索引开始,截取到末尾 |
substring(int beginIndex, int endIndex) | 截取从 beginIndex 到 endIndex-1 的子串 |
关键点:包头不包尾,即“左闭右开”
示例:手机号脱敏处理
import java.util.Scanner;public class demo5 {public static void main(String[] args) {Scanner sc = new Scanner(System.in);String phoneNumber;while (true) {System.out.println("请输入手机号码:");phoneNumber = sc.next();if (phoneNumber.length() <= 11) {break;} else {System.out.println("手机号码长度有误,请重新输入:");}}System.out.println(change(phoneNumber));}public static String change(String phoneNumber) {return phoneNumber.substring(0, 3) + "****" + phoneNumber.substring(7, 11);}
}
输入:
13812345678→ 输出:138****5678
1.4.4 替换
replace() 方法用于将字符串中的某一部分内容替换为新内容。
方法说明
String replace(String oldChar, String newChar):全局替换所有匹配项。- 返回值是新的字符串对象,原字符串不变。
注意:只有返回值才是替换后的结果,必须赋值给变量或直接使用。
示例:敏感词过滤
public class demo5 {public static void main(String[] args) {String talk = "你玩你妈呢,你个傻逼,光送人头!!";String[] arr = {"你妈", "傻逼", "人头"};for (int i = 0; i < arr.length; i++) {talk = talk.replace(arr[i], "***");}System.out.println(talk);}
}
输出结果:
1.4.5 大小写转换
String 提供了便捷的方法用于统一字母的大小写,常用于输入标准化或忽略大小写的匹配场景。
常用方法:
public String toLowerCase():将字符串中所有大写字母转为小写;public String toUpperCase():将字符串中所有小写字母转为大写。
注意:
- 这些方法不会修改原字符串,而是返回一个新字符串;
- 转换遵循当前系统的语言环境(如需指定 Locale,可使用带参数的重载版本)。
示例:
public class demo9 {public static void main(String[] args) {String str = "Hello World";System.out.println(str.toLowerCase()); System.out.println(str.toUpperCase()); }
}
输出:
1.4.6 类型转换
在实际开发中,经常需要在字符串与其他数据类型之间相互转换。
常见方式:
| 场景 | 方法 |
|---|---|
| 其他类型 → String | String.valueOf(任意类型) 或 使用 + "" 拼接 |
| String → 基本类型 | Integer.parseInt(str)、Double.parseDouble(str)、Boolean.parseBoolean(str) 等 |
注意:
String.valueOf()可安全处理null,返回"null"字符串;- 将字符串转为数字时,若内容不符合格式(如
"abc"转int),会抛出NumberFormatException。
示例:
public class demo10 {public static void main(String[] args) {// 其他类型转 StringString s1 = String.valueOf(123); // "123"String s2 = 3.14 + ""; // "3.14"// String 转其他类型int num = Integer.parseInt("100"); // 100double d = Double.parseDouble("3.14"); // 3.14boolean b = Boolean.parseBoolean("true"); // trueSystem.out.println(s1);System.out.println(num);}
}
输出:
1.4.7 案例 —— 金额转换
场景背景

将用户输入的整数金额(如 123)转换为中文大写格式。
实现思路

- 将数字每一位转为对应中文字符;
- 补齐前导零,确保位数统一;
- 按照单位(百万、十万、万、千、百、十、元)进行组合;
完整代码
import java.util.Scanner;public class demo3 {public static void main(String[] args) {// 最高七位的金额转换成大写的金额Scanner sc = new Scanner(System.in);int money;// 输入金额,循环直到输入合法(0 ~ 9999999)while (true) {System.out.println("请输入金额:");money = sc.nextInt();if (0 <= money && money <= 9999999) {break;} else {System.out.println("输入金额有误,请重新输入:");}}// 调用 change 方法将金额转换为中文大写,并输出结果System.out.println(change(money));}/*** 将整数金额转换为中文大写形式(按位处理)*/public static String change(int money) {// 定义中文数字字符数组,索引对应0~9String[] arr = {"零", "壹", "贰", "叁", "肆", "伍", "陆", "柒", "捌", "玖"};// 临时变量,用于提取每一位数字int count = money;String Money = ""; // 存储数字对应的中文字符,从低位到高位拼接// 将金额每一位转换为中文数字(从个位开始)while (count > 0) {int ge = count % 10; // 获取个位数字Money = arr[ge] + Money; // 将对应中文字符加到前面(实现逆序拼接)count /= 10; // 去掉个位,继续处理高位}// 补齐前导零,使字符串长度为7(对应最高七位数)// 例如:123 → "零零零零壹贰叁"int size = 7 - Money.length();for (int i = 0; i < size; i++) {Money = "零" + Money;}// 定义单位数组,按位置对应:百、十、万、千、百、十、元String[] unit = {"佰", "拾", "万", "仟", "佰", "拾", "元"};// 按位拼接每个数字和对应的单位String result = "";for (int i = 0; i < Money.length(); i++) {result = result + Money.charAt(i) + unit[i];}return result; // 返回最终的大写金额字符串}
}
输入:
123→ 输出:
2. StringBuilder
在 Java 中,String 是不可变的(immutable),每次拼接都会创建新的字符串对象,导致内存频繁分配与回收,效率低下。
为了解决这一问题,Java 提供了 StringBuilder 类——一个可变的字符串容器,专为高效字符串操作而设计。
2.1 StringBuilder 的使用场景
- 需要进行大量字符串拼接的操作(如循环中拼接日志、SQL 字符串等);
- 字符串频繁修改(插入、删除、反转等);
比如:将数组
[1,2,3]转换为字符串[1, 2, 3]—— 使用StringBuilder比+快得多!
2.2 StringBuilder 概述
StringBuilder可以看作是一个可变的字符串容器;- 创建后,其内部内容可以被修改(添加、删除、替换、反转等);
- 所有操作都直接作用于同一个对象,不会生成新对象;
- 主要作用是:提高字符串操作的性能与效率。
2.3 StringBuilder 构造方法
| 方法 | 说明 |
|---|---|
public StringBuilder() | 创建一个空白的可变字符串对象,初始容量为 16 字符 |
public StringBuilder(String str) | 根据指定字符串内容创建可变字符串对象 |
// 初始化为 "abc"
StringBuilder sb2 = new StringBuilder("abc");

注意:
StringBuilder是引用类型,必须通过new创建对象。
2.4 StringBuilder基本操作
2.4.1 添加(Append)
append() 方法用于将数据追加到当前字符串末尾。
常用方法:
public StringBuilder append(任意类型):支持String、char、int、boolean、Object等几乎所有类型;- 返回当前
StringBuilder对象,支持链式调用。
注意:
- 所有
append()调用都直接修改原对象;- 自动将非字符串类型转换为字符串形式。
示例:
public class DemoAppend {public static void main(String[] args) {StringBuilder sb = new StringBuilder();sb.append("Hello").append(' ').append(123).append(true);System.out.println(sb); }
}
输出 :
2.4.2 删除(Delete)
delete() 和 deleteCharAt() 用于移除部分或单个字符。
常用方法:
public StringBuilder delete(int start, int end):删除[start, end)区间的字符(左闭右开);public StringBuilder deleteCharAt(int index):删除指定位置的单个字符。
注意:
- 索引越界会抛出
StringIndexOutOfBoundsException;- 删除后后续字符自动前移。
示例:
public class DemoDelete {public static void main(String[] args) {StringBuilder sb = new StringBuilder("abcdef");sb.delete(2, 4); // 删除 "cd"sb.deleteCharAt(0); // 删除首字符 'a'System.out.println(sb);}
}
输出:
2.4.3 插入(Insert)
insert() 方法可在指定位置插入数据。
常用方法:
public StringBuilder insert(int offset, 任意类型):在索引offset处插入内容;- 支持所有基本类型和对象。
注意:
- 插入位置不能超过当前长度(但可等于长度,即在末尾插入);
- 原有字符会向后移动。
示例:
public class DemoInsert {public static void main(String[] args) {StringBuilder sb = new StringBuilder("ac");sb.insert(1, "b"); // 在索引1插入 "b"sb.insert(0, 100); // 在开头插入数字System.out.println(sb);}
}
输出:
2.4.4 替换(Replace)
replace() 用于将一段子串替换为新内容。
常用方法:
public StringBuilder replace(int start, int end, String str):将[start, end)替换为str。
注意:
- 新字符串长度可以与原区间不同;
- 替换后整体长度会相应变化。
示例:
public class DemoReplace {public static void main(String[] args) {StringBuilder sb = new StringBuilder("Hello World");sb.replace(6, 11, "Java");System.out.println(sb); }
}
输出:
2.4.5 翻转(Reverse)
reverse() 可将整个字符串内容反转。
常用方法:
public StringBuilder reverse():反转所有字符顺序。
示例:
public class DemoReverse {public static void main(String[] args) {StringBuilder sb = new StringBuilder("abc");sb.reverse();System.out.println(sb); }
}
输出:
2.4.6 查询与获取
用于获取长度、字符、子串位置等信息。
常用方法:
public int length():返回当前字符个数;public char charAt(int index):获取指定位置的字符;public int indexOf(String str):查找子串首次出现位置;public String substring(int start, int end):截取子串(返回String)。
注意:
length()返回的是实际字符数,不是内部缓冲区容量;substring()返回的是String,不是StringBuilder。
示例:
public class DemoQuery {public static void main(String[] args) {StringBuilder sb = new StringBuilder("Hello");System.out.println("长度:" + sb.length()); System.out.println("第1位:" + sb.charAt(1)); System.out.println("子串位置:" + sb.indexOf("ll")); System.out.println("截取:" + sb.substring(1, 4)); }
}
输出:
2.4.7 转换为 String
最终需将 StringBuilder 转为不可变字符串。
常用方法:
public String toString():返回对应的String对象。
注意:
- 必须调用此方法才能用于需要
String类型的场景(如输出、存储、比较等)。
示例:
public class DemoToString {public static void main(String[] args) {StringBuilder sb = new StringBuilder("OK");String result = sb.toString();System.out.println(result);
}
输出:
2.4.8 清空
StringBuilder 没有 clear() 方法,但可通过设置长度实现清空。
常用方式:
sb.setLength(0):将长度设为 0,保留内部缓冲区,效率高;sb.delete(0, sb.length()):也可清空,但略低效。
示例:
public class DemoClear {public static void main(String[] args) {StringBuilder sb = new StringBuilder("Hello");sb.setLength(0); System.out.println("清空后长度:" + sb.length()); sb.append("New");System.out.println(sb);}
}
输出:
2.5 案例----- 数组拼接问题优化
需求:
定义一个方法,把 int[] arr = {1,2,3,4,5} 按格式 [1, 2, 3, 4, 5] 拼接成字符串。
优化方案:
使用StringBuilder 进行拼接操作,无需频繁创建临时字符串,性能极高。
完整代码:
public class demo2 {public static void main(String[] args) {int[] arr={1,2,3,4,5};System.out.println(arrToString(arr));}public static String arrToString(int[] arr) {StringBuilder sb = new StringBuilder();sb.append("["); // 开始符号for (int i = 0; i < arr.length; i++) {if (i == arr.length - 1) {sb.append(arr[i]); // 最后一个元素不加逗号} else {sb.append(arr[i]).append(", "); // 加逗号和空格}}sb.append("]"); // 结束符号return sb.toString();}
}
输出:
3. StringJoiner
StringJoiner 是 Java 8 引入的一个实用工具类,用于高效、便捷地拼接带分隔符的字符串。相比手动拼接或使用 StringBuilder 拼接分隔符,它能自动处理前缀、后缀和中间分隔逻辑,避免多余的逗号或空格问题。
特别适合拼接集合、数组等场景,如生成
[a, b, c]或a@b@c格式的字符串。
3.1 基本用法
构造方法:
| 构造器 | 说明 |
|---|---|
StringJoiner(CharSequence delimiter) | 指定分隔符(如 ,) |
StringJoiner(CharSequence delimiter, CharSequence prefix, CharSequence suffix) | 指定分隔符、前缀和后缀(如 [ 和 ]) |
注意:
- 所有参数均为
CharSequence类型,支持String、StringBuilder等;- 若未添加任何元素,调用
toString()会返回prefix + suffix(如"[]")。
3.2 常用方法
| 方法 | 说明 |
|---|---|
add(CharSequence element) | 添加一个元素,自动处理分隔(返回 this,支持链式调用) |
toString() | 返回最终拼接结果 |
merge(StringJoiner other) | 合并另一个 StringJoiner 的内容(保留分隔规则) |
toString().length() | 间接获取当前拼接结果的字符长度(StringJoiner 本身无 length() 方法) |
注意:
StringJoiner没有直接提供length()方法,但可以通过先调用toString()再获取长度的方式间接实现。
3.3 示例
3.3.1 基础拼接
import java.util.StringJoiner;public class demo1 {public static void main(String[] args) {StringJoiner sj = new StringJoiner(", ");sj.add("Apple").add("Banana").add("Orange");System.out.println(sj.toString());}
}
输出:
自动在元素间插入
,,无需判断是否为最后一个元素。
3.3.2 带前缀和后缀(模拟数组格式)
import java.util.StringJoiner;public class demo2 {public static void main(String[] args) {StringJoiner sj = new StringJoiner(", ", "[", "]");sj.add("1").add("2").add("3").add("4").add("5");System.out.println(sj.toString()); }
}
输出:
3.4 空值与空容器处理
- 如果从未调用
add(),则:
// 返回 "[]"
new StringJoiner(", ", "[", "]").toString();
- 若需自定义“空时”的显示内容,可通过
setEmptyValue()设置:
StringJoiner sj = new StringJoiner(",");
sj.setEmptyValue("无数据");
System.out.println(sj.toString());
默认空值为
prefix + suffix,若不需要可显式设置。
3.5 与 StringBuilder / 手动拼接对比
| 方式 | 优点 | 缺点 |
|---|---|---|
手动 + 或 StringBuilder | 灵活 | 需手动处理分隔符,易出错(如末尾多逗号) |
StringJoiner | 自动处理分隔、支持前缀后缀、支持链式、可合并 | 需 Java 8+ |
4. StringBuilder 与 StringJoiner 的输出细节
在开发和调试过程中,我们经常需要打印 StringBuilder 或 StringJoiner 对象的内容,以验证拼接结果是否正确。那么,直接打印这些对象会输出内存地址吗?应该如何正确打印?
答案是:不会输出地址,直接打印即可获得期望的字符串内容!
4.1 原因:两者都重写了 toString() 方法
Java 中,System.out.println(obj) 会自动调用 obj.toString()。
而 StringBuilder 和 StringJoiner 均重写了 toString() 方法,返回其内部当前拼接好的字符串内容。
因此,无需手动调用 .toString(),直接打印对象本身即可。
4.2 示例对比
4.2.1 StringBuilder 的打印
public class PrintDemo {public static void main(String[] args) {StringBuilder sb = new StringBuilder();sb.append("Java");sb.append(" ");sb.append("is");sb.append(" fun!");// 推荐:直接打印对象System.out.println(sb); // 功能相同但冗余(不推荐显式写 toString)System.out.println(sb.toString());}
}
输出:
4.2.2 StringJoiner 的打印
import java.util.StringJoiner;public class PrintDemo2 {public static void main(String[] args) {StringJoiner sj = new StringJoiner(", ", "[", "]");sj.add("Apple");sj.add("Banana");sj.add("Cherry");// 直接打印即可System.out.println(sj);}
}
输出:
注意:两种写法结果完全相同,但直接打印更简洁、符合惯例。
5. 链式编程
链式编程是一种常见的编码风格,通过连续调用对象的方法,使代码更加简洁、直观。它的实现依赖于一个关键特性:每个方法执行后返回当前对象本身(或可继续调用的对象),从而支持“点号连续调用”。
链式编程广泛应用于
StringBuilder、流式处理(如Stream)、以及各类工具类和框架中。
5.1 核心原理
实现方式:
- 方法的返回类型为当前类类型;
- 方法体末尾使用
return this;返回自身实例。
特点:
- 无需重复书写对象名;
- 多个操作可在一行内完成;
- 代码逻辑连贯,阅读更流畅。
注意:
- 并非所有类都支持链式调用;
String虽可写出链式形式,但因其不可变性,每一步都会创建新对象,不属于真正意义上的“链式修改”。
5.2 StringBuilder 的典型应用
StringBuilder 几乎所有修改方法都返回 this,是链式编程的最佳实践之一。
public class demo1 {public static void main(String[] args) {StringBuilder sb = new StringBuilder();sb.append("Java").append(' ').append("is").append(' ').append("awesome!").reverse();System.out.println(sb.toString()); }
}
输出:
每次调用
append()或reverse()后仍是对同一个StringBuilder对象的操作,效率高且写法简洁。
对比传统写法:
sb.append("Java"); sb.append(' '); sb.append("is"); // ... 多行重复链式写法显著减少冗余代码。
5.3 String 的“链式写法”
虽然 String 不可变,但也可以连续调用多个方法,形成类似链式的效果:
public class demo2 {public static void main(String[] args) {String result = "HELLO WORLD".toLowerCase() .replace("world", "chain");System.out.println(result);}
}
输出:
注意:
- 每一步操作都会生成新的
String对象;- 在少量操作时可接受,但不适用于循环或高频场景,否则影响性能。
5.4 自定义链式调用(拓展)
若希望自定义类支持链式编程,只需在 setter 等方法中返回 this 即可:
public class User {private String name;private int age;public User setName(String name) {this.name = name;return this; // 关键:返回当前对象}public User setAge(int age) {this.age = age;return this;}public void info() {System.out.println("姓名:" + name + ",年龄:" + age);}public static void main(String[] args) {new User().setName("李四").setAge(20).info(); }
}
输出:
这种设计模式被称为 Fluent Interface(流式接口),被大量现代 Java 框架采用。
6. 字符串底层原理
6.1 字符串拼接的底层原理
6.1.1 情况一:拼接中包含变量
当字符串拼接表达式中包含变量时,Java 编译器不会进行优化,而是将其转换为 StringBuilder 的链式调用。
6.1.1.1 底层执行过程解析:
String s2 = s1 + "b"; // 等价于:
// new StringBuilder().append(s1).append("b").toString();
每执行一次 +,JVM 就会:
- 创建一个
StringBuilder实例;- 调用
append()方法追加内容;- 调用
toString()方法生成新的String对象;- 将结果赋值给变量。
每个
+都会创建一个临时的StringBuilder和一个新的String对象!
6.1.1.2 内存模型示意图:


总共创建了 2 个
StringBuilder对象和 2 个新String对象!
6.1.2 情况二:拼接中全是字面量
6.1.2.1 底层执行过程解析:
String s = "a" + "b" + "c";// 编译后等价于:
//String s = "abc";
编译期优化机制会触发:Java 编译器会在 编译阶段 就将这些字符串合并成一个常量,这个过程称为 “字符串常量折叠”(String Constant Folding)。
无需运行时创建任何对象,直接从常量池加载!
6.1.2.2 内存模型示意图:

6.1.3 JDK 8 字符串拼接的底层原理
在 JDK 8 中,字符串拼接(尤其是涉及变量的 + 操作)的底层实现发生了重要变化。虽然表面上仍表现为“自动转换为 StringBuilder”,但其核心机制已从临时对象堆叠转向了基于数组的高效构造。
6.1.3.1 与 JDK7及以前版本的区别
在 JDK 8 的实际行为中,对于如下代码:
String result = a + b + c; // a, b, c 均为变量
javac 编译器会将其整体视为一个表达式,并生成等价于以下代码的字节码:
StringBuilder sb = new StringBuilder();
sb.append(a).append(b).append(c);
String result = sb.toString();
只创建一个 StringBuilder 实例,而非每遇到一个
+就新建一次。
而这个 StringBuilder 的底层,正是依赖一个可变的 char[] 数组来存储字符内容。
6.1.3.2 底层数据结构:开数组 + 动态扩容

StringBuilder内部维护一个char[] value数组;- 初始容量为 16;
- 当追加内容超过当前容量时,会触发扩容:
新容量 = (原容量 × 2) + 2; - 扩容后,将旧数组内容复制到新数组中;
- 最终调用
toString()时,会复制该 char[] 数组,创建一个新的不可变String对象。
因此,“开数组”是 JDK 8 中字符串拼接高效的核心——它避免了每次拼接都生成中间字符串,而是通过可变数组累积内容,最后一次性构建结果。
6.1.4 小结
- 简单、少量拼接可用
+; - 循环、高频或不确定长度的拼接,应显式使用
StringBuilder并预设初始容量; - 需要分隔符格式化输出时,优先考虑
StringJoiner。
6.2 StringBuilder 提高效率原理
StringBuilder通过 “可变字符数组” + “单次构建” 的方式,显著提升了字符串拼接的效率。
核心思想:集中处理,避免重复创建

StringBuilder 的核心优势在于:
所有要拼接的内容都统一放入一个可变的
char[]数组中,只在最后调用toString()时生成最终的String对象。
这意味着:
- 不会为每一个中间拼接步骤创建新的
String; - 避免了多次对象分配与垃圾回收;
- 减少了堆内存中无用临时对象的数量。
6.3 StringBuilder 源码分析
StringBuilder 是 Java 中用于高效拼接字符串的核心类,其底层实现基于可变的字符数组(char[])。
6.3.1 核心数据结构:char[] 数组
StringBuilder 内部维护一个 char[] value 数组,用于存储所有追加的内容:
// StringBuilder 的核心字段(简化版)
char[] value;
int count; // 当前已使用的字符数量
value:存放实际字符的数组;count:记录当前有效字符个数(即length()返回值);- 初始容量为 16,可通过构造函数指定。
6.3.2 默认容量与扩容机制
默认创建:长度为 16 的 char[] 数组
StringBuilder sb = new StringBuilder();
// 等价于:
// StringBuilder sb = new StringBuilder(16);
此时:
capacity()→ 16length()→ 0
为什么是 16?这是 JVM 经过大量测试后选择的“最优默认值”,兼顾空间利用率和频繁扩容成本。
扩容规则:(原容量 × 2) + 2
当添加内容导致当前容量不足时,会触发扩容操作:
newCapacity = (oldCapacity * 2) + 2;
例如:

- 初始容量:16
- 添加 26 个字符(a~z)→ 超出 16 → 触发扩容
- 新容量 = 16 × 2 + 2 = 34

注意:如果新内容长度大于
newCapacity,则直接以实际需要的长度为准!
扩容的本质:内存复制 + 数组重建
每次扩容都会:
- 创建一个新的、更大容量的
char[]数组; - 将旧数组中的内容复制到新数组;
- 更新
value引用和count值;
这个过程涉及内存拷贝开销,因此在大量拼接时应尽量避免频繁扩容。
6.3.3 如何优化性能?
| 场景 | 推荐做法 |
|---|---|
| 已知最终长度 | 使用 StringBuilder(int capacity) 指定初始容量 |
| 大量拼接 | 预估容量,如 new StringBuilder(100) |
| 循环中拼接 | 不要重复创建 StringBuilder,复用对象 |























