MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

Java中StringBuffer方法的源码解析与理解

2024-11-196.3k 阅读

StringBuffer 概述

在Java中,String类是不可变的,这意味着一旦String对象被创建,其内容就不能被修改。然而,在实际编程中,我们经常需要对字符串进行动态修改,比如拼接、插入、删除等操作。如果使用String类来完成这些操作,每次修改都会创建一个新的String对象,这会导致大量的内存开销和性能问题。为了解决这个问题,Java提供了StringBuffer类。

StringBuffer类是一个可变的字符序列,它允许我们在原有的字符串基础上进行修改,而不需要创建新的对象。StringBuffer类是线程安全的,这意味着多个线程可以安全地访问和修改同一个StringBuffer对象,适用于多线程环境。与之相对的是StringBuilder类,StringBuilder类与StringBuffer类功能基本相同,但StringBuilder类是非线程安全的,在单线程环境下使用StringBuilder类性能更高。

StringBuffer 的构造函数

无参构造函数

public StringBuffer() {
    super(16);
}

这个构造函数创建了一个初始容量为16的StringBuffer对象。这里调用了父类AbstractStringBuilder的构造函数,并传入初始容量16。

带初始容量的构造函数

public StringBuffer(int capacity) {
    super(capacity);
}

此构造函数创建一个指定初始容量的StringBuffer对象。同样调用父类AbstractStringBuilder的构造函数,将指定的容量传递进去。

带初始字符串的构造函数

public StringBuffer(String str) {
    super(str.length() + 16);
    append(str);
}

这个构造函数根据传入的String对象创建一个StringBuffer对象。初始容量为传入字符串的长度加上16。然后通过append方法将传入的字符串追加到StringBuffer中。

StringBuffer 的常用方法

append 方法

  1. append(String str)
public synchronized StringBuffer append(String str) {
    toStringCache = null;
    super.append(str);
    return this;
}

这个方法将指定的字符串追加到StringBuffer的末尾。首先,它将toStringCache设为null,这是因为StringBuffer内容发生了变化,之前缓存的toString结果不再有效。然后调用父类AbstractStringBuilderappend方法完成追加操作,最后返回当前StringBuffer对象,以便进行链式调用。

示例代码:

StringBuffer sb = new StringBuffer("Hello");
sb.append(" World");
System.out.println(sb.toString()); 

输出结果为:Hello World

  1. append(int i)
public synchronized StringBuffer append(int i) {
    toStringCache = null;
    super.append(i);
    return this;
}

此方法将int类型的参数追加到StringBuffer末尾。同样先清空toStringCache,然后调用父类AbstractStringBuilderappend方法。

示例代码:

StringBuffer sb = new StringBuffer("Number: ");
sb.append(123);
System.out.println(sb.toString()); 

输出结果为:Number: 123

insert 方法

  1. insert(int offset, String str)
public synchronized StringBuffer insert(int offset, String str) {
    toStringCache = null;
    super.insert(offset, str);
    return this;
}

该方法在StringBuffer的指定位置offset插入指定的字符串。先清空toStringCache,然后调用父类AbstractStringBuilderinsert方法完成插入操作,并返回当前StringBuffer对象。

示例代码:

StringBuffer sb = new StringBuffer("Hello World");
sb.insert(5, ", ");
System.out.println(sb.toString()); 

输出结果为:Hello, World

  1. insert(int offset, int i)
public synchronized StringBuffer insert(int offset, int i) {
    toStringCache = null;
    super.insert(offset, i);
    return this;
}

此方法在指定位置offset插入int类型的参数。同样的操作流程,先清空toStringCache,再调用父类方法。

示例代码:

StringBuffer sb = new StringBuffer("Value: ");
sb.insert(7, 456);
System.out.println(sb.toString()); 

输出结果为:Value: 456

delete 方法

  1. delete(int start, int end)
public synchronized StringBuffer delete(int start, int end) {
    toStringCache = null;
    super.delete(start, end);
    return this;
}

该方法删除StringBuffer中从start(包括)到end(不包括)的字符序列。先清空toStringCache,然后调用父类AbstractStringBuilderdelete方法,最后返回当前StringBuffer对象。

示例代码:

StringBuffer sb = new StringBuffer("Hello World");
sb.delete(6, 11);
System.out.println(sb.toString()); 

输出结果为:Hello

  1. deleteCharAt(int index)
public synchronized StringBuffer deleteCharAt(int index) {
    toStringCache = null;
    super.deleteCharAt(index);
    return this;
}

此方法删除StringBuffer中指定位置index的字符。同样先清空toStringCache,再调用父类方法。

示例代码:

StringBuffer sb = new StringBuffer("Hello");
sb.deleteCharAt(1);
System.out.println(sb.toString()); 

输出结果为:Hllo

replace 方法

public synchronized StringBuffer replace(int start, int end, String str) {
    toStringCache = null;
    super.replace(start, end, str);
    return this;
}

该方法用指定的字符串str替换StringBuffer中从start(包括)到end(不包括)的字符序列。先清空toStringCache,然后调用父类AbstractStringBuilderreplace方法,最后返回当前StringBuffer对象。

示例代码:

StringBuffer sb = new StringBuffer("Hello World");
sb.replace(6, 11, "Java");
System.out.println(sb.toString()); 

输出结果为:Hello Java

reverse 方法

public synchronized StringBuffer reverse() {
    toStringCache = null;
    super.reverse();
    return this;
}

此方法将StringBuffer中的字符序列反转。先清空toStringCache,然后调用父类AbstractStringBuilderreverse方法,最后返回当前StringBuffer对象。

示例代码:

StringBuffer sb = new StringBuffer("Hello");
sb.reverse();
System.out.println(sb.toString()); 

输出结果为:olleH

capacity 方法

public synchronized int capacity() {
    return value.length;
}

该方法返回StringBuffer的当前容量。这里的valueAbstractStringBuilder类中的字符数组,用于存储StringBuffer的内容。

示例代码:

StringBuffer sb = new StringBuffer("Hello");
System.out.println(sb.capacity()); 

输出结果为:21(因为初始容量为字符串长度5加上16)

length 方法

public synchronized int length() {
    return count;
}

此方法返回StringBuffer中当前字符的个数。countAbstractStringBuilder类中的一个变量,用于记录当前StringBuffer中实际存储的字符数量。

示例代码:

StringBuffer sb = new StringBuffer("Hello");
System.out.println(sb.length()); 

输出结果为:5

ensureCapacity 方法

public synchronized void ensureCapacity(int minimumCapacity) {
    super.ensureCapacity(minimumCapacity);
}

该方法确保StringBuffer的容量至少为minimumCapacity。如果当前容量小于minimumCapacity,则会进行扩容。调用父类AbstractStringBuilderensureCapacity方法来实现。

示例代码:

StringBuffer sb = new StringBuffer("Hello");
sb.ensureCapacity(25);
System.out.println(sb.capacity()); 

输出结果为:25(如果原容量小于25,则会扩容到满足至少25的容量)

AbstractStringBuilder 类的重要作用

StringBuffer类继承自AbstractStringBuilder类,AbstractStringBuilder类提供了StringBuffer类中大部分方法的具体实现。它包含了一些重要的成员变量和方法,对理解StringBuffer的工作原理至关重要。

成员变量

  1. char[] value 这个字符数组用于存储StringBuffer的字符序列。StringBuffer的内容实际上就是存储在这个数组中。

  2. int count count变量记录了StringBuffer中实际存储的字符数量。

方法

  1. append 系列方法AbstractStringBuilder类中,有多个append方法的重载,用于追加不同类型的数据。例如:
public AbstractStringBuilder append(String str) {
    if (str == null)
        str = "null";
    int len = str.length();
    ensureCapacityInternal(count + len);
    str.getChars(0, len, value, count);
    count += len;
    return this;
}

这个append方法首先处理null字符串的情况,将其转换为"null"。然后计算追加后的总长度,通过ensureCapacityInternal方法确保有足够的容量。接着将传入的字符串复制到value数组中,并更新count变量。

  1. insert 系列方法
public AbstractStringBuilder insert(int offset, String str) {
    if ((offset < 0) || (offset > length()))
        throw new StringIndexOutOfBoundsException(offset);
    if (str == null)
        str = "null";
    int len = str.length();
    ensureCapacityInternal(count + len);
    System.arraycopy(value, offset, value, offset + len, count - offset);
    str.getChars(0, len, value, offset);
    count += len;
    return this;
}

insert方法首先检查插入位置是否合法,处理null字符串情况。然后确保有足够的容量,将原数组中从插入位置开始的部分向后移动len个位置,再将传入的字符串插入到指定位置,并更新count变量。

  1. delete 系列方法
public AbstractStringBuilder delete(int start, int end) {
    if (start < 0)
        throw new StringIndexOutOfBoundsException(start);
    if (end > count)
        end = count;
    if (start > end)
        throw new StringIndexOutOfBoundsException();
    int len = end - start;
    if (len > 0) {
        System.arraycopy(value, start + len, value, start, count - end);
        count -= len;
    }
    return this;
}

delete方法检查起始和结束位置是否合法,计算要删除的字符长度len。如果len大于0,则将原数组中从start + len开始的部分向前移动len个位置,并更新count变量。

  1. reverse 方法
public AbstractStringBuilder reverse() {
    boolean hasSurrogates = false;
    int n = count - 1;
    for (int j = (n-1) >> 1; j >= 0; j--) {
        int k = n - j;
        char cj = value[j];
        char ck = value[k];
        value[j] = ck;
        value[k] = cj;
        if (Character.isSurrogatePair(cj) ||
            Character.isSurrogatePair(ck)) {
            hasSurrogates = true;
        }
    }
    if (hasSurrogates) {
        reverseAllValidSurrogatePairs();
    }
    return this;
}

reverse方法通过双指针法将字符数组中的字符进行反转。同时,它还处理了代理对(surrogate pair)的情况,确保在反转过程中代理对的正确性。如果存在代理对,会调用reverseAllValidSurrogatePairs方法进一步处理。

扩容机制

AbstractStringBuilder类中,当StringBuffer的容量不足时,会进行扩容。ensureCapacityInternal方法是扩容的关键。

private void ensureCapacityInternal(int minimumCapacity) {
    // overflow-conscious code
    if (minimumCapacity - value.length > 0) {
        value = Arrays.copyOf(value,
                newCapacity(minimumCapacity));
    }
}

首先检查所需的最小容量minimumCapacity是否大于当前数组value的长度。如果是,则调用newCapacity方法计算新的容量,并通过Arrays.copyOf方法创建一个新的更大的数组,将原数组内容复制到新数组中。

private int newCapacity(int minCapacity) {
    // overflow-conscious code
    int newCapacity = (value.length << 1) + 2;
    if (newCapacity - minCapacity < 0) {
        newCapacity = minCapacity;
    }
    return (newCapacity <= 0 || MAX_ARRAY_SIZE - newCapacity < 0)
        ? hugeCapacity(minCapacity)
        : newCapacity;
}

newCapacity方法默认将新容量设置为当前容量的两倍加2。如果新容量小于所需的最小容量minCapacity,则将新容量设置为minCapacity。如果新容量不满足条件(小于等于0或者超过最大数组大小),则调用hugeCapacity方法处理。

private int hugeCapacity(int minCapacity) {
    if (Integer.MAX_VALUE - minCapacity < 0) {
        throw new OutOfMemoryError();
    }
    return (minCapacity > MAX_ARRAY_SIZE)
        ? minCapacity : MAX_ARRAY_SIZE;
}

hugeCapacity方法处理极端情况,如果所需的最小容量minCapacity超过了Integer.MAX_VALUE,则抛出OutOfMemoryError。否则,如果minCapacity大于最大数组大小MAX_ARRAY_SIZE,则返回minCapacity,否则返回MAX_ARRAY_SIZE

线程安全机制

StringBuffer类的方法大多被synchronized关键字修饰,这使得StringBuffer类是线程安全的。例如append方法:

public synchronized StringBuffer append(String str) {
    toStringCache = null;
    super.append(str);
    return this;
}

synchronized关键字保证了在同一时刻,只有一个线程能够访问被修饰的方法。这就避免了多线程环境下对StringBuffer对象的并发修改导致的数据不一致问题。

然而,由于synchronized关键字带来的同步开销,在单线程环境下使用StringBuffer会有一定的性能损失。相比之下,StringBuilder类没有使用synchronized关键字,在单线程环境下性能更高。

与 StringBuilder 的比较

  1. 线程安全性
    • StringBuffer是线程安全的,适合多线程环境。
    • StringBuilder是非线程安全的,适合单线程环境。
  2. 性能
    • 在单线程环境下,StringBuilder性能更高,因为它没有同步开销。
    • 在多线程环境下,StringBuffer虽然有同步开销,但能保证数据的一致性和线程安全。
  3. 方法和功能
    • StringBuilderStringBuffer的方法基本相同,都继承自AbstractStringBuilder类,提供了类似的字符串操作方法,如appendinsertdelete等。

应用场景

  1. 多线程环境 当在多线程环境下需要对字符串进行动态修改时,应使用StringBuffer。例如,在一个多线程的服务器应用中,多个线程可能需要同时向一个日志缓冲区中追加日志信息,使用StringBuffer可以确保日志信息的正确追加,不会出现数据错乱。
  2. 单线程环境 在单线程环境下,优先使用StringBuilder以获得更高的性能。比如在一个简单的命令行工具中,只在主线程中对字符串进行拼接等操作,使用StringBuilder可以提高执行效率。

总结

通过对StringBuffer类的源码解析,我们深入了解了其构造函数、常用方法、扩容机制、线程安全机制以及与StringBuilder的比较和应用场景。StringBuffer类在Java编程中为处理可变字符串提供了一个重要的工具,尤其在多线程环境下具有不可替代的作用。在实际开发中,我们应根据具体的场景选择合适的字符串处理类,以提高程序的性能和稳定性。