[Java基础]StringBuilder 和 StringBuffer 和 String 比较
String、StringBuffer、StringBuilder的区别
- 可变与不可变:String是不可变字符串对象,StringBuilder和StringBuffer是可变字符串对象(其内部的字符数组长度可变)。
- 是否多线程安全:String中的对象是不可变的,也就可以理解为常量,显然线程安全。StringBuffer 与 StringBuilder 中的方法和功能完全是等价的,只是StringBuffer 中的方法大都采用了synchronized 关键字进行修饰,因此是线程安全的,而 StringBuilder 没有这个修饰,可以被认为是非线程安全的。
- String、StringBuilder、StringBuffer三者的执行效率:StringBuilder > StringBuffer > String 当然这个是相对的,不一定在所有情况下都是这样。比如String str = "hello"+ "world"的效率就比 StringBuilder st = new StringBuilder().append("hello").append("world")要高。因此,这三个类是各有利弊,应当根据不同的情况来进行选择
使用:
- 当字符串相加操作或者改动较少的情况下,建议使用 String str="hello"这种形式;
- 当字符串相加操作较多的情况下,建议使用StringBuilder,如果采用了多线程,则使用StringBuffer。
可变性
-
String 是不可变的。
-
StringBuilder 与 StringBuffer 都继承自 AbstractStringBuilder 类,在 AbstractStringBuilder 中也是使用字符数组保存字符串,不过没有使用 final 和 private 关键字修饰,最关键的是这个 AbstractStringBuilder 类还提供了很多修改字符串的方法。比如 append 方法。从如下代码中可以看到,append方法并不是创建一个新的string对象,而是在原有的string中的char数组中直接进行修改(当然如果数组长度不够了会进行扩容产生一个新的数组)。
abstract class AbstractStringBuilder implements Appendable, CharSequence {
char[] value;
public AbstractStringBuilder append(String str) {
if (str == null)
return appendNull();
int len = str.length();
ensureCapacityInternal(count + len);
str.getChars(0, len, value, count);
count += len;
return this;
}
//...
}
线程安全性
- String 中的对象是不可变的,也就可以理解为常量,线程安全。
AbstractStringBuilder 是 StringBuilder 与 StringBuffer 的公共父类,定义了一些字符串的基本操作,如 expandCapacity、append、insert、indexOf 等公共方法。
-
StringBuilder 并没有对方法进行加同步锁,所以是非线程安全的。
-
StringBuffer 对方法加了同步锁或者对调用的方法加了同步锁,所以是线程安全的。
性能
- 每次对 String 类型进行改变的时候,都会生成一个新的 String 对象,然后将指针指向新的 String 对象。
- StringBuffer 每次都会对 StringBuffer 对象本身进行操作,而不是生成新的对象并改变对象引用。
- 相同情况下使用 StringBuilder 相比使用 StringBuffer 仅能获得 10%~15% 左右的性能提升,但却要冒多线程不安全的风险。
对于三者使用的总结:
- 操作少量的数据: 适用 String
- 单线程操作字符串缓冲区下操作大量数据: 适用 StringBuilder
- 多线程操作字符串缓冲区下操作大量数据: 适用 StringBuffer
在Java中,String、StringBuilder 和 StringBuffer 是三个用于处理字符串的类,但它们在特性和用法上有所不同。以下是每个类的详细解释以及它们的区别:
1. String
- 不可变性:
String对象是不可变的。一旦创建了一个String对象,其值就不能改变。如果需要修改字符串,则会创建一个新的String对象。 - 线程安全:由于
String对象是不可变的,因此是线程安全的。 - 性能:由于每次修改都会创建新的对象,频繁的字符串操作可能会导致性能问题和内存开销的增加。
String str = "Hello";
str += " World"; // 新建了一个字符串对象,str引用了新的"Hello World"对象
2. StringBuilder
- 可变性:
StringBuilder是可变的,意味着它的内容可以被修改。 - 线程不安全:因为它不是同步的,因此
StringBuilder是线程不安全的,适用于单线程环境。 - 性能:由于不需要创建新的对象来进行修改,
StringBuilder在频繁操作字符串时性能表现更好。
StringBuilder sb = new StringBuilder("Hello");
sb.append(" World"); // 修改了同一个StringBuilder对象
3. StringBuffer
- 可变性:
StringBuffer也是可变的,可以修改其内容。 - 线程安全:
StringBuffer是线程安全的,因为它的方法是同步的,使用了synchronied关键字,适用于多线程环境。 - 性能:由于使用了同步,即使在单线程环境中,
StringBuffer的性能可能会比StringBuilder略差。
StringBuffer sbf = new StringBuffer("Hello");
sbf.append(" World"); // 修改了同一个StringBuffer对象
总结区别
- 不可变性:
String是不可变的,StringBuilder和StringBuffer是可变的。 - 线程安全:
String是线程安全的,因为它是不可变的;StringBuffer是线程安全的,因为它的方法是同步的;StringBuilder是线程不安全的,它的设计目的是用在单线程环境中。 - 性能:
StringBuilder通常比StringBuffer性能更好,适用于单线程环境;StringBuffer适用于需要线程安全的多线程环境;而String由于不可变性,频繁操作字符串时性能较差。
选择哪个类要根据具体的应用场景和需求来决定。如果在单线程环境中进行大量字符串操作,StringBuilder 是首选;如果在多线程环境中操作字符串,StringBuffer 是优选;而对于完全不可变的字符串,使用 String 是最佳选择。
为什么
StringBuilder和StringBuffer可变
在 Java 中,StringBuilder 和 StringBuffer 是可变的字符序列,主要用于在字符串频繁修改(如追加、插入、删除等)时提高性能。它们与 String 类的最大区别在于:String 是不可变的,而 StringBuilder 和 StringBuffer 是可变的。这种可变性通过底层的动态数组和方法操作实现,以下是它们如何实现可变性的详细解释。
1. 底层使用可变的字符数组
StringBuilder 和 StringBuffer 的核心是一个 可变的字符数组(char[])。这个数组用于存储当前的字符串内容,当字符串内容改变时,字符数组的内容会被直接修改,而不需要像 String 那样每次修改都创建新的对象。
初始容量和扩展
-
当创建
StringBuilder或StringBuffer时,它们会分配一个初始容量的字符数组(通常默认为 16 个字符的容量)。这个初始容量是可以指定的,如果没有指定,默认的容量是 16。 -
当字符数组的容量不足以容纳新的字符时,
StringBuilder和StringBuffer会动态扩展数组的大小。扩展的过程是通过 创建一个新的、更大的字符数组,然后将旧数组中的内容复制到新数组中。
例子:
StringBuilder sb = new StringBuilder();
sb.append("Hello");
sb.append(" World");
System.out.println(sb.toString());
- 原理:在上面的代码中,
StringBuilder的内部字符数组被初始化为默认容量 16。当append()方法被调用时,字符串 "Hello" 和 "World" 被直接追加到内部的字符数组中,数组的大小自动扩展以适应更多的字符。
2. 可变性实现机制:修改内部数组
StringBuilder 和 StringBuffer 的 append()、insert()、delete() 等方法直接操作内部的字符数组,而不是像 String 那样返回一个新的对象。这使得这些操作能够在原地修改字符串内容,避免了频繁创建新对象的开销。
例子:append() 操作
StringBuilder sb = new StringBuilder("Hello");
sb.append(" World"); // 修改了内部的字符数组,而不是创建新的对象
System.out.println(sb.toString()); // 输出 "Hello World"
StringBuilder的append()方法会将新字符串的字符添加到内部字符数组的末尾。如果当前数组容量不足,则扩展数组容量,再进行追加操作。
内部实现原理(伪代码简化):
public StringBuilder append(String str) {
if (str == null) {
str = "null";
}
int newCount = count + str.length(); // 计算新字符串需要的总长度
if (newCount > value.length) {
expandCapacity(newCount); // 如果容量不足,扩展数组
}
str.getChars(0, str.length(), value, count); // 将新字符串复制到字符数组
count = newCount; // 更新字符数组的使用长度
return this;
}
expandCapacity(newCount):当字符数组容量不足时,StringBuilder和StringBuffer会调用此方法扩展数组容量。通常,新容量会比当前容量大约扩大为 当前容量的两倍加上 2,以避免频繁的数组扩展操作。
3. StringBuilder 与 StringBuffer 的区别
虽然 StringBuilder 和 StringBuffer 的底层实现方式非常相似(都是基于可变字符数组的动态扩展),但它们之间有一个重要的区别:
-
StringBuffer是线程安全的:StringBuffer的方法是同步的(synchronized),因此在多线程环境下,多个线程可以安全地对同一个StringBuffer实例进行操作。每个方法调用都会获取一个锁,保证线程安全。 -
StringBuilder不是线程安全的:StringBuilder的方法没有同步,因此在单线程环境下,它的性能比StringBuffer更高,因为它避免了不必要的锁定开销。StringBuilder更适合在单线程场景中使用。
4. 可变性与性能的关系
由于 StringBuilder 和 StringBuffer 是可变的,它们的设计大大减少了内存消耗和提升了性能,尤其在大量修改字符串的场景中,例如在循环中拼接字符串。String 的不可变性意味着每次字符串修改都要创建新的对象,而 StringBuilder 和 StringBuffer 则是在原有字符数组上进行修改,从而避免了额外的对象创建。
性能对比:
// String 不可变的操作,每次都会创建新的字符串对象
String str = "";
for (int i = 0; i < 1000; i++) {
str += i; // 由于 String 是不可变的,每次拼接都会创建新的对象
}
// StringBuilder 可变操作,直接在原有字符数组上修改
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 1000; i++) {
sb.append(i); // 直接在同一个对象的字符数组上追加
}
- 在第一个例子中,
String每次拼接都会创建一个新的字符串对象,效率低且内存消耗高。 - 在第二个例子中,
StringBuilder直接在原字符数组上修改,性能更高,且内存消耗较少。
5. 总结
StringBuilder和StringBuffer的可变性:它们的可变性通过底层的 动态字符数组 实现。在字符串内容改变时,直接在内部数组上进行修改,而不需要创建新的对象。随着内容的增加,字符数组会自动扩展以适应更多字符。StringBuffer是线程安全的,而StringBuilder不是:StringBuffer的方法通过同步机制保证线程安全,但在单线程环境下,StringBuilder由于不需要同步,性能更好。- 性能优势:由于
StringBuilder和StringBuffer可以在同一个对象上进行多次修改,它们在频繁修改字符串时性能显著优于String。
在开发中,如果需要频繁地修改字符串,建议使用 StringBuilder(单线程)或 StringBuffer(多线程),这样可以大幅提高程序的性能。

浙公网安备 33010602011771号