Java中StringBuffer方法的源码解析与理解
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 方法
- append(String str)
public synchronized StringBuffer append(String str) {
toStringCache = null;
super.append(str);
return this;
}
这个方法将指定的字符串追加到StringBuffer
的末尾。首先,它将toStringCache
设为null
,这是因为StringBuffer
内容发生了变化,之前缓存的toString
结果不再有效。然后调用父类AbstractStringBuilder
的append
方法完成追加操作,最后返回当前StringBuffer
对象,以便进行链式调用。
示例代码:
StringBuffer sb = new StringBuffer("Hello");
sb.append(" World");
System.out.println(sb.toString());
输出结果为:Hello World
- append(int i)
public synchronized StringBuffer append(int i) {
toStringCache = null;
super.append(i);
return this;
}
此方法将int
类型的参数追加到StringBuffer
末尾。同样先清空toStringCache
,然后调用父类AbstractStringBuilder
的append
方法。
示例代码:
StringBuffer sb = new StringBuffer("Number: ");
sb.append(123);
System.out.println(sb.toString());
输出结果为:Number: 123
insert 方法
- insert(int offset, String str)
public synchronized StringBuffer insert(int offset, String str) {
toStringCache = null;
super.insert(offset, str);
return this;
}
该方法在StringBuffer
的指定位置offset
插入指定的字符串。先清空toStringCache
,然后调用父类AbstractStringBuilder
的insert
方法完成插入操作,并返回当前StringBuffer
对象。
示例代码:
StringBuffer sb = new StringBuffer("Hello World");
sb.insert(5, ", ");
System.out.println(sb.toString());
输出结果为:Hello, World
- 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 方法
- 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
,然后调用父类AbstractStringBuilder
的delete
方法,最后返回当前StringBuffer
对象。
示例代码:
StringBuffer sb = new StringBuffer("Hello World");
sb.delete(6, 11);
System.out.println(sb.toString());
输出结果为:Hello
- 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
,然后调用父类AbstractStringBuilder
的replace
方法,最后返回当前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
,然后调用父类AbstractStringBuilder
的reverse
方法,最后返回当前StringBuffer
对象。
示例代码:
StringBuffer sb = new StringBuffer("Hello");
sb.reverse();
System.out.println(sb.toString());
输出结果为:olleH
capacity 方法
public synchronized int capacity() {
return value.length;
}
该方法返回StringBuffer
的当前容量。这里的value
是AbstractStringBuilder
类中的字符数组,用于存储StringBuffer
的内容。
示例代码:
StringBuffer sb = new StringBuffer("Hello");
System.out.println(sb.capacity());
输出结果为:21
(因为初始容量为字符串长度5加上16)
length 方法
public synchronized int length() {
return count;
}
此方法返回StringBuffer
中当前字符的个数。count
是AbstractStringBuilder
类中的一个变量,用于记录当前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
,则会进行扩容。调用父类AbstractStringBuilder
的ensureCapacity
方法来实现。
示例代码:
StringBuffer sb = new StringBuffer("Hello");
sb.ensureCapacity(25);
System.out.println(sb.capacity());
输出结果为:25
(如果原容量小于25,则会扩容到满足至少25的容量)
AbstractStringBuilder 类的重要作用
StringBuffer
类继承自AbstractStringBuilder
类,AbstractStringBuilder
类提供了StringBuffer
类中大部分方法的具体实现。它包含了一些重要的成员变量和方法,对理解StringBuffer
的工作原理至关重要。
成员变量
-
char[] value 这个字符数组用于存储
StringBuffer
的字符序列。StringBuffer
的内容实际上就是存储在这个数组中。 -
int count
count
变量记录了StringBuffer
中实际存储的字符数量。
方法
- 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
变量。
- 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
变量。
- 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
变量。
- 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 的比较
- 线程安全性
StringBuffer
是线程安全的,适合多线程环境。StringBuilder
是非线程安全的,适合单线程环境。
- 性能
- 在单线程环境下,
StringBuilder
性能更高,因为它没有同步开销。 - 在多线程环境下,
StringBuffer
虽然有同步开销,但能保证数据的一致性和线程安全。
- 在单线程环境下,
- 方法和功能
StringBuilder
和StringBuffer
的方法基本相同,都继承自AbstractStringBuilder
类,提供了类似的字符串操作方法,如append
、insert
、delete
等。
应用场景
- 多线程环境
当在多线程环境下需要对字符串进行动态修改时,应使用
StringBuffer
。例如,在一个多线程的服务器应用中,多个线程可能需要同时向一个日志缓冲区中追加日志信息,使用StringBuffer
可以确保日志信息的正确追加,不会出现数据错乱。 - 单线程环境
在单线程环境下,优先使用
StringBuilder
以获得更高的性能。比如在一个简单的命令行工具中,只在主线程中对字符串进行拼接等操作,使用StringBuilder
可以提高执行效率。
总结
通过对StringBuffer
类的源码解析,我们深入了解了其构造函数、常用方法、扩容机制、线程安全机制以及与StringBuilder
的比较和应用场景。StringBuffer
类在Java编程中为处理可变字符串提供了一个重要的工具,尤其在多线程环境下具有不可替代的作用。在实际开发中,我们应根据具体的场景选择合适的字符串处理类,以提高程序的性能和稳定性。