String 是我们用到非常多的一个类,对于 String 做大量的操作,如果只使用 String 的话,效率没有那么高。一般会推荐使用 StringBuffer 和 Stringbuilder 来做字符串的操作。
那么 StringBuffer 和 StringBuilder 的区别是什么呢?
StringBuffer 是线程安全的,因为它里面的方法都被 synchronized 关键字修饰,例如 append 方法:
@Override
public synchronized StringBuffer append(String str) {
toStringCache = null;
super.append(str);
return this;
}
StringBuilder 是线程不安全的,他里面没有使用 synchronized 关键字修饰
@Override
public StringBuilder append(String str) {
super.append(str);
return this;
}
其中两个类都是在调用父类的 append 方法,只是 StringBuffer 通过 synchronized 关键字来保证线程安全,当然这样做同时也会降低效率。除非是在单线程环境下,并且非常追求速率的情况下使用 StringBuilder,其他情况下还是推荐使用 StringBuffer 来做字符串的操作。这篇文章也不是对比两个类的优缺点,直接去看这两个类的父类 AbstractStringBuilder 。贴出父类中的 append 方法:
public AbstractStringBuilder append(String str) {
if (str == null)
return appendNull();
int len = str.length();
ensureCapacityInternal(count + len); // 扩容
str.getChars(0, len, value, count); // 添加新的 String 到尾部
count += len; // 长度加上添加进来的长度
return this;
}
实际上不管是哪一个,都是在使用父类的方法以及成员变量。
/** 存储字符的数组 */
char[] value;
/** 字符数组中已经使用得到长度 */
int count;
父类 AbstractStringBuilder 中维护了一个存储字符的数组 value,用来保存字符串的全部字符。count 是用来表示使用了多长的数组。
接下来看一下扩容方法:
private void ensureCapacityInternal(int minimumCapacity) {
// overflow-conscious code
if (minimumCapacity - value.length > 0)
expandCapacity(minimumCapacity);
}
void expandCapacity(int minimumCapacity) {
int newCapacity = value.length * 2 + 2; // 扩容为原来的 2 倍加 2
if (newCapacity - minimumCapacity < 0) // 如果扩容后的长度仍然小于最小扩容长度,则新长度赋值为最小扩容长度
newCapacity = minimumCapacity;
if (newCapacity < 0) { // 新长度为负数,超过 int 的最大值,变为了负数
if (minimumCapacity < 0) // overflow // 最小的扩容长度为负数,也是超过了 int 最大值
throw new OutOfMemoryError();
newCapacity = Integer.MAX_VALUE; // 如果最小扩容长度没有超过 int 最大值,但是原长度翻倍加2后超过了,则把新长度赋值为 int 最大值
}
value = Arrays.copyOf(value, newCapacity); // 调用 Arrays.copyOf 生成新长度的数组
}
扩容方法就是扩容为原来的 2 倍再加 2 ,然后判断新长度的合法性,不合法会抛出 OOM ,合法会复制一个新长度的数组覆盖原来的数组。
真正添加 String 的方法是 getChars
public void getChars(int srcBegin, int srcEnd, char dst[], int dstBegin) {
if (srcBegin < 0) {
throw new StringIndexOutOfBoundsException(srcBegin);
}
if (srcEnd > value.length) {
throw new StringIndexOutOfBoundsException(srcEnd);
}
if (srcBegin > srcEnd) {
throw new StringIndexOutOfBoundsException(srcEnd - srcBegin);
}
// 执行到这里,说明没有出现下标越界,调用 arraycopy 把该字符串追加到尾部
System.arraycopy(value, srcBegin, dst, dstBegin, srcEnd - srcBegin);
}
此时,StringBuffer 的 append 方法才算结束执行。
看完在尾部添加 String 的 append 方法,看一下可以在指定下标处添加的 insert 方法,直接贴出调用的父类的 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); // 扩容
// 从 offset 处开始之后的字符都向后移动 len 长度的位置
System.arraycopy(value, offset, value, offset + len, count - offset);
// 把新添加的 String 添加到 offset 位置处
str.getChars(value, offset);
count += len;
return this;
}
检查下标的合法性后就做扩容操作,然后在将指定位置处以后的元素向后移动新加 String 长度的距离,然后将新 String 添加至指定位置。
删除指定区间的 delete 方法如下,依然用 synchronized 控制,并调用父类方法
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) {
// 把 start + len 位置开始的元素向前移动 len 个距离
System.arraycopy(value, start+len, value, start, count-end);
count -= len;
}
return this;
}
也是使用了 arraycopy 来操作内部维护的字符数组。
最后看一下字符串反转方法 reverse
public AbstractStringBuilder reverse() {
boolean hasSurrogates = false;
int n = count - 1; // 得到下标最大值
/* 循环,从中间向两边移动,并交换位置
count = 长度 n = 下表最大值 j = 下标减一后除
如果是偶数个->user count = 4, n = 3, j = 1 交换下标为 1 和 3-1 的两个值,即 s 和 e,然后向两头移动
如果奇数个->hello count = 5, n = 4, j = 1 交换下标为 1 和 4-1 的两个值,即 e 和第二个 l,然后向两头移动
*/
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.isSurrogate(cj) ||
Character.isSurrogate(ck)) {
hasSurrogates = true;
}
}
if (hasSurrogates) {
reverseAllValidSurrogatePairs();
}
return this;
}
看懂循环时下标的指向就很容易理解了,如果光看不好理解的话,可以跟我一样用 debug 看一下值的变化,一步一步的看,就能很容易理解了。