Java中StringBuffer与StringBuilder的详细解析
目录
一、基本概念
1.核心区别
2.底层实现
3.StringBuffer与StringBuilder内部实现与扩容机制
⑴核心源码结构
⑵底层数据结构
⑶扩容机制
⑷初始化容量
⑸添加字符的流程(以 append(String str) 为例)
⑹ 扩容示例
二、StringBuffer和StringBuilder构造方法
1. 无参构造方法
2. 带初始容量参数的构造方法
3. 带字符串参数的构造方法
4. 带 CharSequence 参数的构造方法(Java 5 及以上)
三、常用方法
1. 追加方法
2. 删除方法
3. 插入方法
4. 替换方法
5. 反转方法
6. 设置方法
7. 获取字符方法
8. 查找方法
9. 长度和子字符串方法
10. 转换为字符串方法
四、String、StringBuilder 和 StringBuffer 的效率对比和优化策略的详细解析
1. 问题根源:String 的不可变性
2. StringBuilder 的高效性
3. StringBuffer 的线程安全代价
4. 性能优化策略
(1) 预分配容量
(2) 避免隐式转换
5. 效率对比总结
6. 实际测试数据
7. 总结与建议
一、基本概念
String类:
String类是不可变的,一旦创建了一个String对象,它的值就不能被改变。如果对String对象进行拼接、替换等操作,实际上是创建了一个新的String对象。
StringBuffer类:
StringBuffer是线程安全的可变字符序列。它的所有公共方法都是同步的,这意味着在多线程环境下可以安全地使用,但在单线程环境下会有一定的性能开销。
StringBuilder类:
StringBuilder是StringBuffer的非线程安全版本,从 Java 5 开始引入。它的方法没有进行同步处理,因此在单线程环境下性能比StringBuffer更高。
在Java中,StringBuffer和StringBuilder是专为频繁字符串拼接设计的可变字符串类,其核心区别和实现细节如下:
1.核心区别
线程安全:
①StringBuffer是线程安全的(方法使用synchronized修饰),适用于多线程环境。
②StringBuilder非线程安全,单线程下效率更高。
出现时间:
StringBuffer自Java 1.0引入,StringBuilder于Java 5新增。
2.底层实现
Java 8及之前:底层使用char[ ]存储字符。
Java 9及之后:优化为byte[ ],根据字符编码动态选择Latin-1(1字节/字符)或UTF-16(2字节/字符),节省内存。
3.StringBuffer与StringBuilder内部实现与扩容机制
在Java中,StringBuilder和StringBuffer的核心实现逻辑继承自同一个父类 AbstractStringBuilder,两者的区别仅在于线程安全性(StringBuffer的方法用synchronized修饰)。
⑴核心源码结构
①继承关系
StringBuilder 和 StringBuffer 均继承自 AbstractStringBuilder:
// JDK 17 源码
public final class StringBuilder extends AbstractStringBuilder {
// 方法未同步
}
public final class StringBuffer extends AbstractStringBuilder {
// 方法用 synchronized 修饰
}
AbstractStringBuilder:包含核心的字符数组存储(byte[ ] value)、扩容逻辑和操作方法(append(), insert()等)。
② 扩容代码位置
扩容方法定义在 AbstractStringBuilder 中,StringBuilder 和 StringBuffer 直接继承这些方法。 例如 ensureCapacityInternal(), newCapacity() 等扩容逻辑,两者完全共用同一份实现。
它们的底层数据结构、扩容机制完全一致。以下是基于 JDK 17 源码的深入解析:
⑵底层数据结构
两者均通过 动态字符数组 存储数据,继承自 AbstractStringBuilder,核心字段如下:
// AbstractStringBuilder 类源码
abstract class AbstractStringBuilder {
byte[] value; // JDK 9 后改为 byte[](优化内存,支持压缩字符串)
int count; // 当前实际字符数(非数组长度)
// ...
}
value:存储字符的数组,初始容量由构造器决定。
count:记录当前实际存储的字符数(即字符串长度)。
JDK 9+优化:从 char[ ] 改为 byte[ ],结合编码标志(coder)压缩存储(LATIN1或UTF16),节省内存。
⑶扩容机制
当添加字符导致 count + 1 > value.length 时触发扩容。核心方法是 ensureCapacityInternal(int minimumCapacity)。
源码分析(JDK 17)
/**
* 确保内部数组的容量足够,如果当前容量不足,则触发扩容操作。
*
* @param minimumCapacity 所需的最小容量
*/
private void ensureCapacityInternal(int minimumCapacity) {
// 当前容量不足时触发扩容
// minimumCapacity 为所需的最小容量,value.length 为当前数组的容量
// 若所需最小容量大于当前数组容量,则需要进行扩容
if (minimumCapacity - value.length > 0) {
// 调用 newCapacity 方法计算新的容量,并使用 Arrays.copyOf 方法将原数组复制到新容量的数组中
value = Arrays.copyOf(value, newCapacity(minimumCapacity));
}
}
/**
* 计算新的数组容量。
*
* @param minCapacity 所需的最小容量
* @return 计算得到的新容量
*/
private int newCapacity(int minCapacity) {
// 获取当前数组的容量
int oldCapacity = value.length;
// 默认扩容策略:旧容量 * 2 + 2
// 使用左移操作符 << 实现乘以 2 的效果,效率更高
int newCapacity = (oldCapacity << 1) + 2;
// 若扩容后的容量仍小于所需的最小容量,则直接使用所需的最小容量作为新容量
if (newCapacity - minCapacity < 0) {
newCapacity = minCapacity;
}
// 检查计算得到的新容量是否合法,是否超过最大容量(MAX_ARRAY_SIZE,通常为 Integer.MAX_VALUE - 8)
// 如果新容量小于等于 0 或者超过了最大容量限制,则调用 hugeCapacity 方法处理
return (newCapacity <= 0 || MAX_ARRAY_SIZE - newCapacity < 0)
? hugeCapacity(minCapacity)
: newCapacity;
}
/**
* 处理大容量需求的情况。
*
* @param minCapacity 所需的最小容量
* @return 最终确定的大容量
* @throws OutOfMemoryError 如果所需容量超过了 Integer.MAX_VALUE,抛出内存溢出异常
*/
private int hugeCapacity(int minCapacity) {
// 检查所需的最小容量是否超过了 Integer.MAX_VALUE,如果超过则会发生溢出
if (Integer.MAX_VALUE - minCapacity < 0) {
// 若超过 Integer.MAX_VALUE,抛出内存溢出异常
throw new OutOfMemoryError();
}
// 如果所需的最小容量大于最大容量(MAX_ARRAY_SIZE),则使用所需的最小容量
// 否则使用最大容量
return (minCapacity > MAX_ARRAY_SIZE) ? minCapacity : MAX_ARRAY_SIZE;
}
扩容规则
默认扩容策略:新容量 = 旧容量 * 2 + 2。
特殊场景:若所需容量(minCapacity)超过默认扩容后的值,直接使用 minCapacity。
容量上限:最大为 Integer.MAX_VALUE – 8(MAX_ARRAY_SIZE),超过则可能抛出 OutOfMemoryError。
⑷初始化容量
不同构造器初始化容量方式不同:
①无参构造器
// StringBuilder 无参构造器
public StringBuilder() {
super(16); // 调用 AbstractStringBuilder(int capacity)
}
// AbstractStringBuilder 初始化
AbstractStringBuilder(int capacity) {
value = (capacity == 0)
? new byte[0]
: new byte[capacity];
coder = (byte)(String.COMPACT_STRINGS ? String.LATIN1 : String.UTF16);
}
默认容量为16(若未指定)。
②指定初始容量
public StringBuilder(int capacity) {
super(capacity);
}
③基于字符串初始化
public StringBuilder(String str) {
super(str.length() + 16); // 初始容量 = 字符串长度 + 16
append(str);
}
⑸添加字符的流程(以 append(String str)
为例)
public AbstractStringBuilder append(String str) {
if (str == null) {
return appendNull(); // 处理 null
}
int len = str.length();
ensureCapacityInternal(count + len); // 检查容量
putStringAt(count, str); // 将字符串复制到 value 数组
count += len; // 更新字符数
return this;
}
容量检查:调用 ensureCapacityInternal,若不足则扩容。
数据复制:将新字符串内容复制到 value 数组中。
更新长度:count 增加新字符数。
⑹ 扩容示例
假设初始容量为 5,依次添加字符串 "HelloWorld":
操作 | 当前容量 | 扩容逻辑 | 新容量 |
---|---|---|---|
初始容量 | 5 | – | 5 |
添加 "Hello" (5字符) | 5 → 不足 | 新容量 = 5*2 + 2 = 12 | 12 |
继续添加 "World" (5) | 12 – 10 = 2 | 剩余容量足够,无需扩容 | 12 |
再添加 "!" (1) | 12 – 11 = 1 | 剩余容量足够,无需扩容 | 12 |
⑺操作方法的差异
StringBuilder 的方法:直接调用父类方法,无同步。
// StringBuilder 的 append 方法
@Override
public StringBuilder append(String str) {
super.append(str); // 调用 AbstractStringBuilder.append()
return this;
}
StringBuffer 的方法:通过 synchronized 保证线程安全。
// StringBuffer 的 append 方法
@Override
public synchronized StringBuffer append(String str) {
toStringCache = null; // JDK 17 前存在,现已移除
super.append(str); // 同样调用 AbstractStringBuilder.append()
return this;
}
维度 | StringBuilder | StringBuffer |
---|---|---|
线程安全 | 非线程安全 | 线程安全(方法用synchronized ) |
扩容机制 | 与StringBuffer完全相同 | 与StringBuilder完全相同 |
性能 | 高(无同步开销) | 较低(同步开销) |
底层存储 | 共享AbstractStringBuilder的byte[ ] value |
同上 |
二、StringBuffer和StringBuilder构造方法
1. 无参构造方法
功能:创建一个初始容量为 16 个字符的空对象。当存储的字符数量超过 16 时,会自动进行扩容。
示例代码:
public class ConstructorExample {
public static void main(String[] args) {
// 使用无参构造方法创建 StringBuffer 对象
StringBuffer stringBuffer = new StringBuffer();
// 使用无参构造方法创建 StringBuilder 对象
StringBuilder stringBuilder = new StringBuilder();
System.out.println("StringBuffer 初始长度: " + stringBuffer.length());
System.out.println("StringBuilder 初始长度: " + stringBuilder.length());
}
}
运行结果:
2. 带初始容量参数的构造方法
功能:创建一个指定初始容量的空对象。当需要处理大量字符且大致知道所需容量时,使用此构造方法可以避免多次扩容带来的性能开销。
public class ConstructorExample {
public static void main(String[] args) {
// 创建一个初始容量为 32 的 StringBuffer 对象
StringBuffer stringBuffer = new StringBuffer(32);
// 创建一个初始容量为 32 的 StringBuilder 对象
StringBuilder stringBuilder = new StringBuilder(32);
System.out.println("StringBuffer 初始容量: " + stringBuffer.capacity());
System.out.println("StringBuilder 初始容量: " + stringBuilder.capacity());
}
}
运行结果:
3. 带字符串参数的构造方法
功能:创建一个包含指定字符串内容的对象,初始容量为字符串的长度加上 16。
public class ConstructorExample {
public static void main(String[] args) {
String str = "Hello";
// 创建一个包含指定字符串的 StringBuffer 对象
StringBuffer stringBuffer = new StringBuffer(str);
// 创建一个包含指定字符串的 StringBuilder 对象
StringBuilder stringBuilder = new StringBuilder(str);
System.out.println("StringBuffer 内容: " + stringBuffer.toString());
System.out.println("StringBuilder 内容: " + stringBuilder.toString());
System.out.println("StringBuffer 容量: " + stringBuffer.capacity());
System.out.println("StringBuilder 容量: " + stringBuilder.capacity());
}
}
运行结果:
4. 带 CharSequence 参数的构造方法(Java 5 及以上)
功能:创建一个包含指定 CharSequence 内容的对象。CharSequence 是一个接口,String、StringBuffer、StringBuilder 等类都实现了该接口。
public class ConstructorExample {
public static void main(String[] args) {
CharSequence charSequence = "World";
// 创建一个包含指定 CharSequence 的 StringBuffer 对象
StringBuffer stringBuffer = new StringBuffer(charSequence);
// 创建一个包含指定 CharSequence 的 StringBuilder 对象
StringBuilder stringBuilder = new StringBuilder(charSequence);
System.out.println("StringBuffer 内容: " + stringBuffer.toString());
System.out.println("StringBuilder 内容: " + stringBuilder.toString());
}
}
运行结果:
三、常用方法
1. 追加方法
append(Type data)
语法:StringBuffer append(Type data) 或 StringBuilder append(Type data)
功能:将指定类型的数据追加到当前对象的末尾。Type 可以是 String、char、int、double 等多种类型。
示例:
public class AppendExample {
public static void main(String[] args) {
// StringBuffer append
StringBuffer stringBuffer = new StringBuffer();
stringBuffer.append("Hello").append(" ").append("World");
System.out.println("StringBuffer append 结果: " + stringBuffer.toString());
// StringBuilder append
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append(123).append(true);
System.out.println("StringBuilder append 结果: " + stringBuilder.toString());
}
}
运行结果:
2. 删除方法
delete(int start, int end)
语法:StringBuffer delete(int start, int end) 或 StringBuilder delete(int start, int end)
功能:移除从索引 start(包含)到索引 end(不包含)之间的字符序列。
示例:
public class DeleteExample {
public static void main(String[] args) {
// StringBuffer delete
StringBuffer stringBuffer = new StringBuffer("HelloWorld");
stringBuffer.delete(5, 10);
System.out.println("StringBuffer delete 结果: " + stringBuffer.toString());
// StringBuilder delete
StringBuilder stringBuilder = new StringBuilder("HelloWorld");
stringBuilder.delete(5, 10);
System.out.println("StringBuilder delete 结果: " + stringBuilder.toString());
}
}
运行结果:
deleteCharAt(int index)
语法:StringBuffer deleteCharAt(int index) 或 StringBuilder deleteCharAt(int index)
功能:移除指定索引位置的字符。
示例:
public class DeleteCharAtExample {
public static void main(String[] args) {
// StringBuffer deleteCharAt
StringBuffer stringBuffer = new StringBuffer("Hello");
stringBuffer.deleteCharAt(1);
System.out.println("StringBuffer deleteCharAt 结果: " + stringBuffer.toString());
// StringBuilder deleteCharAt
StringBuilder stringBuilder = new StringBuilder("Hello");
stringBuilder.deleteCharAt(1);
System.out.println("StringBuilder deleteCharAt 结果: " + stringBuilder.toString());
}
}
运行结果:
3. 插入方法
insert(int offset, String str)
语法:StringBuffer insert(int offset, String str) 或 StringBuilder insert(int offset, String str)
功能:在指定的偏移量 offset 处插入指定的字符串 str。
示例:
public class InsertExample {
public static void main(String[] args) {
// StringBuffer insert
StringBuffer stringBuffer = new StringBuffer("Hello");
stringBuffer.insert(5, " World");
System.out.println("StringBuffer insert 结果: " + stringBuffer.toString());
// StringBuilder insert
StringBuilder stringBuilder = new StringBuilder("Hello");
stringBuilder.insert(5, " World");
System.out.println("StringBuilder insert 结果: " + stringBuilder.toString());
}
}
运行结果:
4. 替换方法
replace(int start, int end, String str)
语法:StringBuffer replace(int start, int end, String str) 或 StringBuilder replace(int start, int end, String str)
功能:将从索引 start(包含)到索引 end(不包含)之间的字符序列替换为指定的字符串 str。
示例:
public class ReplaceExample {
public static void main(String[] args) {
// StringBuffer replace
StringBuffer stringBuffer = new StringBuffer("HelloWorld");
stringBuffer.replace(5, 10, " Java");
System.out.println("StringBuffer replace 结果: " + stringBuffer.toString());
// StringBuilder replace
StringBuilder stringBuilder = new StringBuilder("HelloWorld");
stringBuilder.replace(5, 10, " Java");
System.out.println("StringBuilder replace 结果: " + stringBuilder.toString());
}
}
运行结果:
5. 反转方法
reverse()
语法:StringBuffer reverse() 或 StringBuilder reverse()
功能:将当前对象中的字符序列反转。
示例:
public class ReverseExample {
public static void main(String[] args) {
// StringBuffer reverse
StringBuffer stringBuffer = new StringBuffer("Hello");
stringBuffer.reverse();
System.out.println("StringBuffer reverse 结果: " + stringBuffer.toString());
// StringBuilder reverse
StringBuilder stringBuilder = new StringBuilder("Hello");
stringBuilder.reverse();
System.out.println("StringBuilder reverse 结果: " + stringBuilder.toString());
}
}
运行结果:
6. 设置方法
setCharAt(int index, char ch)
语法:void setCharAt(int index, char ch)
功能:将指定索引位置的字符替换为指定的字符 ch。
示例:
public class SetCharAtExample {
public static void main(String[] args) {
// StringBuffer setCharAt
StringBuffer stringBuffer = new StringBuffer("Hello");
stringBuffer.setCharAt(1, 'a');
System.out.println("StringBuffer setCharAt 结果: " + stringBuffer.toString());
// StringBuilder setCharAt
StringBuilder stringBuilder = new StringBuilder("Hello");
stringBuilder.setCharAt(1, 'a');
System.out.println("StringBuilder setCharAt 结果: " + stringBuilder.toString());
}
}
运行结果:
setLength(int newLength)
语法:void setLength(int newLength)
功能:设置当前对象的长度。如果 newLength 小于当前长度,则会截断字符序列;如果 newLength 大于当前长度,则会在末尾填充空字符('\0')。
示例:
public class SetLengthExample {
public static void main(String[] args) {
// StringBuffer setLength
StringBuffer stringBuffer = new StringBuffer("Hello");
stringBuffer.setLength(3);
System.out.println("StringBuffer setLength 截断结果: " + stringBuffer.toString());
stringBuffer.setLength(5);
System.out.println("StringBuffer setLength 扩充结果: " + stringBuffer.toString());
// StringBuilder setLength
StringBuilder stringBuilder = new StringBuilder("Hello");
stringBuilder.setLength(3);
System.out.println("StringBuilder setLength 截断结果: " + stringBuilder.toString());
stringBuilder.setLength(5);
System.out.println("StringBuilder setLength 扩充结果: " + stringBuilder.toString());
}
}
运行结果:
7. 获取字符方法
charAt(int index)
语法:char charAt(int index)
功能:返回指定索引位置的字符。
示例:
public class CharAtExample {
public static void main(String[] args) {
// StringBuffer charAt
StringBuffer stringBuffer = new StringBuffer("Hello");
char c1 = stringBuffer.charAt(1);
System.out.println("StringBuffer charAt 结果: " + c1);
// StringBuilder charAt
StringBuilder stringBuilder = new StringBuilder("Hello");
char c2 = stringBuilder.charAt(1);
System.out.println("StringBuilder charAt 结果: " + c2);
}
}
运行结果:
8. 查找方法
indexOf(String str)
语法:int indexOf(String str)
功能:返回指定字符串 str 在当前对象中第一次出现的索引位置。如果未找到,则返回 -1。
示例:
public class IndexOfExample {
public static void main(String[] args) {
// StringBuffer indexOf
StringBuffer stringBuffer = new StringBuffer("HelloWorld");
int index1 = stringBuffer.indexOf("World");
System.out.println("StringBuffer indexOf 结果: " + index1);
// StringBuilder indexOf
StringBuilder stringBuilder = new StringBuilder("HelloWorld");
int index2 = stringBuilder.indexOf("World");
System.out.println("StringBuilder indexOf 结果: " + index2);
}
}
运行结果:
indexOf(String str, int fromIndex)
语法:int indexOf(String str, int fromIndex)
功能:从指定的索引 fromIndex 开始,返回指定字符串 str 在当前对象中第一次出现的索引位置。如果未找到,则返回 -1。
示例:
public class IndexOfWithStartExample {
public static void main(String[] args) {
// StringBuffer indexOf 带起始位置
StringBuffer stringBuffer = new StringBuffer("HelloWorldHello");
int index1 = stringBuffer.indexOf("Hello", 6);
System.out.println("StringBuffer indexOf 带起始位置结果: " + index1);
// StringBuilder indexOf 带起始位置
StringBuilder stringBuilder = new StringBuilder("HelloWorldHello");
int index2 = stringBuilder.indexOf("Hello", 6);
System.out.println("StringBuilder indexOf 带起始位置结果: " + index2);
}
}
运行结果:
lastIndexOf(String str)
语法:int lastIndexOf(String str)
功能:返回指定字符串 str 在当前对象中最后一次出现的索引位置。如果未找到,则返回 -1。
示例:
public class LastIndexOfExample {
public static void main(String[] args) {
// StringBuffer lastIndexOf
StringBuffer stringBuffer = new StringBuffer("HelloWorldHello");
int index1 = stringBuffer.lastIndexOf("Hello");
System.out.println("StringBuffer lastIndexOf 结果: " + index1);
// StringBuilder lastIndexOf
StringBuilder stringBuilder = new StringBuilder("HelloWorldHello");
int index2 = stringBuilder.lastIndexOf("Hello");
System.out.println("StringBuilder lastIndexOf 结果: " + index2);
}
}
运行结果:
lastIndexOf(String str, int fromIndex)
语法:int lastIndexOf(String str, int fromIndex)
功能:从指定的索引 fromIndex 开始向前搜索,返回指定字符串 str 在当前对象中最后一次出现的索引位置。如果未找到,则返回 -1。
示例:
public class LastIndexOfWithStartExample {
public static void main(String[] args) {
// StringBuffer lastIndexOf 带起始位置
StringBuffer stringBuffer = new StringBuffer("HelloWorldHello");
int index1 = stringBuffer.lastIndexOf("Hello", 9);
System.out.println("StringBuffer lastIndexOf 带起始位置结果: " + index1);
// StringBuilder lastIndexOf 带起始位置
StringBuilder stringBuilder = new StringBuilder("HelloWorldHello");
int index2 = stringBuilder.lastIndexOf("Hello", 9);
System.out.println("StringBuilder lastIndexOf 带起始位置结果: " + index2);
}
}
运行结果:
9. 长度和子字符串方法
length()
语法:int length()
功能:返回当前对象中字符序列的长度。
示例:
public class LengthExample {
public static void main(String[] args) {
// StringBuffer length
StringBuffer stringBuffer = new StringBuffer("Hello");
int len1 = stringBuffer.length();
System.out.println("StringBuffer length 结果: " + len1);
// StringBuilder length
StringBuilder stringBuilder = new StringBuilder("Hello");
int len2 = stringBuilder.length();
System.out.println("StringBuilder length 结果: " + len2);
}
}
运行结果:
substring(int start)
语法:String substring(int start)
功能:返回从指定索引 start 开始到字符序列末尾的子字符串。
示例:
public class SubstringSingleParamExample {
public static void main(String[] args) {
// StringBuffer substring 单参数
StringBuffer stringBuffer = new StringBuffer("HelloWorld");
String sub1 = stringBuffer.substring(5);
System.out.println("StringBuffer substring 单参数结果: " + sub1);
// StringBuilder substring 单参数
StringBuilder stringBuilder = new StringBuilder("HelloWorld");
String sub2 = stringBuilder.substring(5);
System.out.println("StringBuilder substring 单参数结果: " + sub2);
}
}
运行结果:
substring(int start, int end)
语法:String substring(int start, int end)
功能:返回从索引 start(包含)到索引 end(不包含)之间的子字符串。
示例:
public class SubstringTwoParamsExample {
public static void main(String[] args) {
// StringBuffer substring 双参数
StringBuffer stringBuffer = new StringBuffer("HelloWorld");
String sub1 = stringBuffer.substring(0, 5);
System.out.println("StringBuffer substring 双参数结果: " + sub1);
// StringBuilder substring 双参数
StringBuilder stringBuilder = new StringBuilder("HelloWorld");
String sub2 = stringBuilder.substring(0, 5);
System.out.println("StringBuilder substring 双参数结果: " + sub2);
}
}
运行结果:
10. 转换为字符串方法
toString()
语法:String toString()
功能:将当前对象中的字符序列转换为一个不可变的 String 对象。
示例:
public class ToStringExample {
public static void main(String[] args) {
// StringBuffer toString
StringBuffer stringBuffer = new StringBuffer("Hello");
String str1 = stringBuffer.toString();
System.out.println("StringBuffer toString 结果: " + str1);
// StringBuilder toString
StringBuilder stringBuilder = new StringBuilder("Hello");
String str2 = stringBuilder.toString();
System.out.println("StringBuilder toString 结果: " + str2);
}
}
运行结果:
四、String
、StringBuilder
和 StringBuffer
的效率对比和优化策略的详细解析
1. 问题根源:String 的不可变性
String 是不可变类,每次拼接(如 s += i)都会触发以下操作:
①创建一个新的 StringBuilder 对象(隐式创建)。
②调用 append() 方法将当前 s 和 i 拼接。
③调用 toString() 生成新的 String 对象。
④旧的 String 对象成为垃圾,等待 GC 回收。
示例代码分析:
String s = "";
for (int i = 0; i < 100000; i++) {
s += i; // 等价于 s = new StringBuilder().append(s).append(i).toString();
}
问题:循环 10 万次会创建 10 万个 StringBuilder 和 String 对象,导致:
频繁内存分配和对象复制。
GC 压力剧增(大量短期对象进入年轻代,触发频繁 Minor GC)。
结果:耗时 3827 毫秒,效率极低。
2. StringBuilder 的高效性
StringBuilder 是可变字符序列,底层基于动态扩容的 byte[ ](或 char[ ]),直接修改原数组,避免频繁对象创建。
示例代码分析:
StringBuilder s = new StringBuilder();
for (int i = 0; i < 100000; i++) {
s.append(i); // 直接操作底层数组,无需创建新对象
}
优势:
单次对象创建:只初始化一个 StringBuilder 对象。
动态扩容优化:底层数组按需扩容(扩容策略为 原容量*2 + 2)。
无额外 GC 压力:对象复用,减少垃圾产生。
结果:耗时 7 毫秒,效率提升近 400 倍。
3. StringBuffer 的线程安全代价
StringBuffer 与 StringBuilder 功能相同,但所有方法用 synchronized 修饰以保证线程安全。 效率对比:
单线程环境:StringBuffer 因同步锁开销,效率低于 StringBuilder(测试中可能慢 10%~20%)。
多线程环境:需共享变量时,StringBuffer 是唯一选择。
4. 性能优化策略
(1) 预分配容量
默认容量:StringBuilder 初始容量为 16。
扩容成本:每次扩容需复制旧数组到新数组,时间复杂度为 O(n)。
优化方案:预估最终字符串长度,直接指定初始容量。
StringBuilder sb = new StringBuilder(100000); // 直接分配足够空间,避免扩容
5. 效率对比总结
类名 | 线程安全 | 单线程效率 | 适用场景 |
---|---|---|---|
String |
不可变 | 极低 | 少量字符串操作或常量定义 |
StringBuilder |
不安全 | 最高 | 单线程频繁字符串操作 |
StringBuffer |
安全 | 中等 | 多线程共享变量的字符串操作 |
6. 实际测试数据
操作类型 | 循环次数 | 耗时(毫秒) |
---|---|---|
String 拼接 |
100,000 | 3827 |
StringBuilder |
100,000 | 7 |
StringBuffer |
100,000 | 12~15 |
7. 总结与建议
优先选择
StringBuilder
:在单线程环境下,始终优先使用StringBuilder
。避免
String
拼接循环:尤其在数据量大时,直接使用StringBuilder
。预分配容量:预估字符串长度,减少扩容次数。
多线程场景用
StringBuffer
:仅在需要线程安全时使用,否则性能损耗明显。
理解这些差异和优化策略,可以显著提升字符串操作的性能,减少不必要的资源消耗。
作者:magic 245