--- title: "02-StringBuilder系列" created: 2025-11-25 tags: - Java --- # StringBuilder系列 ## 核心概念 Java提供了三种字符串类型: - **String**:不可变,适合少量操作 - **StringBuilder**:可变,非线程安全,适合单线程大量拼接 - **StringBuffer**:可变,线程安全,适合多线程场景 --- ## 一、三者对比 ### 1.1 核心区别 | 特性 | String | StringBuilder | StringBuffer | | --- | --- | --- | --- | | 可变性 | 不可变 | 可变 | 可变 | | 线程安全 | 是(不可变) | 否 | 是(synchronized) | | 性能 | 拼接慢 | 最快 | 较快 | | 引入版本 | JDK 1.0 | JDK 1.5 | JDK 1.0 | | 使用场景 | 少量操作 | 单线程拼接 | 多线程拼接 | ### 1.2 性能对比 ```java // 测试:拼接10万次 // String: 约5000ms(每次创建新对象) // StringBuilder: 约5ms // StringBuffer: 约10ms(同步开销) // String拼接(慢) String s = ""; for (int i = 0; i < 100000; i++) { s += i; // 每次创建新对象 } // StringBuilder拼接(快) StringBuilder sb = new StringBuilder(); for (int i = 0; i < 100000; i++) { sb.append(i); // 原地修改 } String result = sb.toString(); ``` ### 1.3 线程安全对比 ```java // StringBuffer:线程安全 public synchronized StringBuffer append(String str) { toStringCache = null; super.append(str); return this; } // StringBuilder:非线程安全 public StringBuilder append(String str) { super.append(str); return this; } ``` --- ## 二、StringBuilder底层实现 ### 2.1 内部结构 ```java abstract class AbstractStringBuilder { byte[] value; // 存储字符数据(JDK 9+) byte coder; // 编码标识 int count; // 实际字符数 // JDK 8及之前 // char[] value; } ``` ### 2.2 构造方法 ```java // 默认容量16 StringBuilder sb1 = new StringBuilder(); // capacity = 16 // 指定初始容量 StringBuilder sb2 = new StringBuilder(100); // capacity = 100 // 根据字符串初始化 StringBuilder sb3 = new StringBuilder("hello"); // capacity = 5 + 16 = 21 ``` ### 2.3 扩容机制 ```java // 扩容公式:newCapacity = (oldCapacity << 1) + 2 // 即:新容量 = 旧容量 * 2 + 2 private int newCapacity(int minCapacity) { int newCapacity = (value.length << 1) + 2; // 2倍+2 if (newCapacity - minCapacity < 0) { newCapacity = minCapacity; // 如果还不够,直接用所需容量 } return (newCapacity <= 0 || MAX_ARRAY_SIZE - newCapacity < 0) ? hugeCapacity(minCapacity) : newCapacity; } ``` **扩容过程示例:** ```java StringBuilder sb = new StringBuilder(); // capacity = 16 sb.append("12345678901234567"); // 需要17,触发扩容 // 新容量 = 16 * 2 + 2 = 34 ``` ### 2.4 为什么扩容是2倍+2? - **2倍**:平衡内存使用和扩容频率 - **+2**:避免初始容量为0时无法扩容 --- ## 三、常用方法 ### 3.1 追加方法 ```java StringBuilder sb = new StringBuilder(); // append():追加各种类型 sb.append("hello"); sb.append(123); sb.append(true); sb.append(3.14); // 链式调用 sb.append("a").append("b").append("c"); ``` ### 3.2 插入方法 ```java StringBuilder sb = new StringBuilder("hello"); sb.insert(0, "say "); // "say hello" sb.insert(4, "123"); // "say 123hello" ``` ### 3.3 删除方法 ```java StringBuilder sb = new StringBuilder("hello world"); sb.delete(5, 11); // "hello"(删除索引5到10) sb.deleteCharAt(0); // "ello"(删除索引0的字符) ``` ### 3.4 替换与反转 ```java StringBuilder sb = new StringBuilder("hello"); sb.replace(0, 2, "HE"); // "HEllo" sb.reverse(); // "olleH" ``` ### 3.5 其他方法 ```java StringBuilder sb = new StringBuilder("hello"); sb.length(); // 5(字符数) sb.capacity(); // 21(容量) sb.setLength(3); // "hel"(截断) sb.charAt(0); // 'h' sb.setCharAt(0, 'H'); // "Hel" sb.toString(); // 转为String ``` --- ## 四、使用场景选择 ### 4.1 选择决策树 ``` 需要字符串操作? ├── 少量操作(<10次)→ String └── 大量操作 ├── 单线程 → StringBuilder └── 多线程 → StringBuffer ``` ### 4.2 具体场景 ```java // 场景1:简单拼接 → String String name = firstName + " " + lastName; // 场景2:循环拼接 → StringBuilder StringBuilder sb = new StringBuilder(); for (String item : list) { sb.append(item).append(","); } // 场景3:多线程共享 → StringBuffer StringBuffer buffer = new StringBuffer(); // 多个线程同时操作buffer // 场景4:SQL拼接 → StringBuilder StringBuilder sql = new StringBuilder("SELECT * FROM user WHERE 1=1"); if (name != null) { sql.append(" AND name = '").append(name).append("'"); } ``` ### 4.3 预估容量优化 ```java // ❌ 不指定容量,可能多次扩容 StringBuilder sb = new StringBuilder(); // ✅ 预估容量,减少扩容 StringBuilder sb = new StringBuilder(1000); // 计算预估容量 int estimatedSize = list.size() * averageItemLength; StringBuilder sb = new StringBuilder(estimatedSize); ``` --- ## 五、编译器优化 ### 5.1 字符串拼接优化 ```java // 源码 String s = "a" + "b" + "c"; // JDK 8编译后(StringBuilder) String s = new StringBuilder().append("a").append("b").append("c").toString(); // JDK 9+编译后(invokedynamic) // 使用StringConcatFactory,更高效 ``` ### 5.2 循环拼接的问题 ```java // ❌ 编译器无法优化循环 String s = ""; for (int i = 0; i < 100; i++) { s += i; // 每次循环都创建新的StringBuilder } // 反编译后 String s = ""; for (int i = 0; i < 100; i++) { s = new StringBuilder(s).append(i).toString(); // 每次new } // ✅ 手动优化 StringBuilder sb = new StringBuilder(); for (int i = 0; i < 100; i++) { sb.append(i); } String s = sb.toString(); ``` --- ## 六、高频面试题 ### Q1: String、StringBuilder、StringBuffer的区别? **答:** | 特性 | String | StringBuilder | StringBuffer | | --- | --- | --- | --- | | 可变性 | 不可变 | 可变 | 可变 | | 线程安全 | 是 | 否 | 是 | | 性能 | 拼接慢 | 最快 | 较快 | | 场景 | 少量操作 | 单线程拼接 | 多线程拼接 | ### Q2: StringBuilder的扩容机制是什么? **答:** - 默认初始容量:16 - 扩容公式:新容量 = 旧容量 × 2 + 2 - 如果计算后仍不够,直接使用所需容量 - 扩容时创建新数组并复制数据 ### Q3: 为什么StringBuilder比String快? **答:** - String不可变,每次拼接都创建新对象 - StringBuilder可变,在原数组上修改 - 循环拼接时,String创建n个对象,StringBuilder只创建1个 ### Q4: StringBuffer是如何保证线程安全的? **答:** - 所有公共方法都用synchronized修饰 - 同一时刻只有一个线程能执行修改操作 - 代价是性能损失(约2倍) ### Q5: 什么时候用String,什么时候用StringBuilder? **答:** - **String**:字符串不需要修改,或只有少量拼接 - **StringBuilder**:单线程环境下大量字符串拼接 - **StringBuffer**:多线程环境下共享字符串拼接 ### Q6: StringBuilder初始容量设置多少合适? **答:** - 预估最终字符串长度 - 设置为预估长度的1.2~1.5倍 - 避免设置过大浪费内存,过小频繁扩容 --- ## 七、最佳实践 ### ✅ 推荐做法 ```java // 1. 循环拼接用StringBuilder StringBuilder sb = new StringBuilder(); for (String item : list) { sb.append(item); } // 2. 预估容量 StringBuilder sb = new StringBuilder(list.size() * 20); // 3. 链式调用 sb.append("name=").append(name).append("&age=").append(age); // 4. 多线程用StringBuffer StringBuffer buffer = new StringBuffer(); ``` ### ❌ 避免做法 ```java // 1. 循环中用String拼接 String s = ""; for (int i = 0; i < 1000; i++) { s += i; // 性能差 } // 2. 单线程用StringBuffer StringBuffer buffer = new StringBuffer(); // 不必要的同步开销 // 3. 不预估容量 StringBuilder sb = new StringBuilder(); // 可能多次扩容 ``` --- ## 相关链接 - [[01-String详解|01-String详解]] - String的不可变性和常量池 - [[03-BigDecimal|03-BigDecimal]] - 精确数值计算 --- ## 记忆口诀 ``` String不可变,拼接创对象 Builder可变快,单线程首选 Buffer线程安,synchronized保 扩容二倍加二,预估容量好 ``` --- ⬅️ [[01-String详解|String详解]] 🏠 [[00-Java|00-Java]] ➡️ [[03-BigDecimal|BigDecimal]]