StringBuffer和StringBuilder

StringBuffer和StringBuilder的区别

由于字符串不可变,所以当遇到字符串拼接(尤其是使用+号操作符)的时候,就需要考量性能的问题,你不能毫无顾虑地生产太多 String 对象,对珍贵的内存造成不必要的压力。

于是 Java 就设计了一个专门用来解决此问题的 StringBuffer 类。

public final class StringBuffer extends AbstractStringBuilder implements Serializable, CharSequence {

    public StringBuffer() {
        super(16);
    }
    
    public synchronized StringBuffer append(String str) {
        super.append(str);
        return this;
    }

    public synchronized String toString() {
        return new String(value, 0, count);
    }

    // 其他方法
}

不过,由于 StringBuffer 操作字符串的方法加了 synchronized关键字 进行了同步,主要是考虑到多线程环境下的安全问题,所以如果在非多线程环境下,执行效率就会比较低,因为加了没必要的锁。

于是 Java 就给 StringBuffer “生了个兄弟”,名叫 StringBuilder,说,“孩子,你别管线程安全了,你就在单线程环境下使用,这样效率会高得多,如果要在多线程环境下修改字符串,你到时候可以使用 ThreadLocal 来避免多线程冲突。” 至此StringBuffer 出现了。

ThreadLocal后续会讲。

public final class StringBuilder extends AbstractStringBuilder
    implements java.io.Serializable, CharSequence
{
    // ...

    public StringBuilder append(String str) {
        super.append(str);
        return this;
    }

    public String toString() {
        // Create a copy, don't share the array
        return new String(value, 0, count);
    }

    // ...
}

除了类名不同,方法没有加 synchronized,基本上完全一样。

并且实际开发中StringBuilder 使用率也更高

StringBuilder的使用

当编译器遇到 + 号这个操作符的时候,会将 new String("二哥") + new String("三妹") 这行代码解释为以下代码:

new StringBuilder().append("二哥").append("三妹").toString();

这个过程是我们看不见的,但这正是 Java 的“智能”之处,它可以在编译的时候偷偷地帮我们做很多优化,这样既可以提高我们的开发效率(+ 号写起来比创建 StringBuilder 对象便捷得多),也不会影响 JVM 的执行效率。

0: new           #2                  // class java/lang/StringBuilder
3: dup
4: invokespecial #3                  // Method java/lang/StringBuilder."<init>":()V
7: new           #4                  // class java/lang/String
10: dup
11: ldc           #5                  // String 二哥
13: invokespecial #6                  // Method java/lang/String."<init>":(Ljava/lang/String;)V
16: invokevirtual #7                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
19: new           #4                  // class java/lang/String
22: dup
23: ldc           #8                  // String 三妹
25: invokespecial #6                  // Method java/lang/String."<init>":(Ljava/lang/String;)V
28: invokevirtual #7                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
31: invokevirtual #9                  // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
34: areturn

可以看到 Java 编译器将字符串拼接操作(+)转换为了 StringBuilder 对象的 append 方法,然后再调用 StringBuilder 对象的 toString 方法返回拼接后的字符串。

 

StringBuilder的内部实现

我们来看一下 StringBuilder 的 toString 方法:

public String toString() {
    return new String(value, 0, count);
}
value

value 是一个 char 类型的数组

/**
 * The value is used for character storage.
 */
char[] value;

在 StringBuilder 对象创建时,会为 value 分配一定的内存空间(初始容量 16),用于存储字符串。

/**
 * Constructs a string builder with no characters in it and an
 * initial capacity of 16 characters.
 */
public StringBuilder() {
    super(16);
}

随着字符串的拼接,value 数组的长度会不断增加,因此在 StringBuilder 对象的实现中,value 数组的长度是可以动态扩展,像ArrayList。

继续来看 StringBuilder 的 toString 方法:

public String toString() {
    return new String(value, 0, count);
}
 count

value 用于存储 StringBuilder 对象中包含的字符序列。count 是一个 int 类型的变量,表示字符序列的长度。toString() 方法会调用 new String(value, 0, count),使用 value 数组中从 0 开始的前 count 个元素创建一个新的字符串对象,并将其返回。

再来看一下 append 方法:

public StringBuilder append(String str) {
    super.append(str);
    return this;
}

实际上是调用了 AbstractStringBuilder 中的 append(String str) 方法。在 AbstractStringBuilder 中,append(String str) 方法会检查当前字符序列中的字符是否够用,如果不够用则会进行扩容,并将指定字符串追加到字符序列的末尾。

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;
}

append(String str) 方法将指定字符串追加到当前字符序列中。如果指定字符串为 null,则追加字符串 "null";否则会检查指定字符串的长度,然后根据当前字符序列中的字符数和指定字符串的长度来判断是否需要扩容。

如果需要扩容,则会调用 ensureCapacityInternal(int minimumCapacity) 方法。扩容之后,将指定字符串的字符拷贝到字符序列中。

来看一下 ensureCapacityInternal 方法:

private void ensureCapacityInternal(int minimumCapacity) {
    // 不够用了,扩容
    if (minimumCapacity - value.length > 0)
        expandCapacity(minimumCapacity);
}

void expandCapacity(int minimumCapacity) {
    // 扩容策略:新容量为旧容量的两倍加上 2
    int newCapacity = value.length * 2 + 2;
    // 如果新容量小于指定的最小容量,则新容量为指定的最小容量
    if (newCapacity - minimumCapacity < 0)
        newCapacity = minimumCapacity;
    // 如果新容量小于 0,则新容量为 Integer.MAX_VALUE
    if (newCapacity < 0) {
        if (minimumCapacity < 0) // overflow
            throw new OutOfMemoryError();
        newCapacity = Integer.MAX_VALUE;
    }
    // 将字符序列的容量扩容到新容量的大小
    value = Arrays.copyOf(value, newCapacity);
}

ensureCapacityInternal(int minimumCapacity) 方法用于确保当前字符序列的容量至少等于指定的最小容量 minimumCapacity。如果当前容量小于指定的容量,就会为字符序列分配一个新的内部数组。新容量的计算方式如下:

  • 如果指定的最小容量大于当前容量,则新容量为两倍的旧容量加上 2。为什么要加 2 呢?对于非常小的字符串(比如空的或只有一个字符的 StringBuilder),仅仅将容量加倍可能仍然不足以容纳更多的字符。在这种情况下,+ 2 提供了一个最小的增长量,确保即使对于很小的初始容量,扩容后也能至少添加一些字符而不需要立即再次扩容。
  • 如果指定的最小容量小于等于当前容量,则不会进行扩容,直接返回当前对象。

在进行扩容之前,ensureCapacityInternal(int minimumCapacity) 方法会先检查当前字符序列的容量是否足够,如果不足就会调用 expandCapacity(int minimumCapacity) 方法进行扩容。expandCapacity(int minimumCapacity) 方法首先计算出新容量,然后使用 Arrays.copyOf(char[] original, int newLength) 方法将原字符数组扩容到新容量的大小。 


StringBuilder内容参考于 javaBetter.cn 大佬的网站

### 回答1: StringBufferStringBuilderJava 中的字符串操作类。它们都是可变的字符序列,可用于在运行时构造字符串。 两者的主要区别在于线程安全性。StringBuffer 是线程安全的,它的每个方法都是同步的,因此可以在多线程环境中使用。StringBuilder 是非线程安全的,它的方法不是同步的,因此不能在多线程环境中使用。 因此,如果不需要线程安全,建议使用 StringBuilder,因为它比 StringBuffer 更快。如果需要线程安全,则使用 StringBuffer。 ### 回答2: StringBufferStringBuilderJava中的两个字符串操作类,它们都可以用来处理可变的字符串。 StringBuffer是一个线程安全的类,适用于多线程环境下。它提供了多个方法来操作字符串,如添加、插入、删除、替换等。StringBuffer对象是可变的,可以在原有字符串的基础上进行修改,而不会创建新的对象。这使得在频繁修改字符串的情况下,StringBuffer比String更高效。 StringBuffer使用同步机制来保证线程安全,因此在多线程环境中频繁地操作字符串时,建议使用StringBufferStringBuilder也是一个可变的字符串类,与StringBuffer类似,它也提供了多个方法来操作字符串。然而,StringBuilder不是线程安全的,因此在单线程环境下使用效果更好。相比于StringBufferStringBuilder的操作速度更快,因为它不需要进行同步控制。 一般来说,在单线程环境下,如果需要频繁地进行字符串拼接、替换等操作,建议使用StringBuilder;而在多线程环境下,或者需要保证线程安全性时,应使用StringBuffer。 总之,StringBufferStringBuilder在处理可变字符串时都非常有效。具体使用哪个类可以根据实际情况来选择,考虑到线程安全和性能方面的需求。 ### 回答3: StringBufferStringBuilder都是Java中用于处理可变字符串的类。它们的主要区别在于线程安全性和性能。 StringBuffer是一个线程安全的类,它的所有公共方法都被synchronized修饰,因此多个线程可以同时访问一个StringBuffer对象而不会出现问题。这使得StringBuffer适用于在多线程环境中进行字符串操作的场景。然而,由于同步的代价是一定的,所以StringBuffer的性能相对较低。 StringBuilder是一个非线程安全的类,它的方法没有被synchronized修饰,因此在多线程环境中使用StringBuilder可能会出现不可预期的结果。但是,由于没有同步的开销,StringBuilder的性能比StringBuffer高很多。 因此,如果在单线程环境中进行字符串操作,推荐使用StringBuilder,因为它的性能更好。但是在多线程环境中进行字符串操作时,为了保证数据的一致性,应该使用StringBuffer。 需要注意的是,无论是StringBuffer还是StringBuilder,它们都继承自AbstractStringBuilder,并且都有append、insert、delete等方法,可以方便地对字符串进行修改。另外,它们还都重写了toString方法,可以将字符串对象转化为String类型的字符串。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值