整体看String、StringBuffer、StringBuilder三个类间的类结构:
上面的类结构图中,无论是直接依赖还是间接依赖,StringBuffer和StringBuilder都是以CharSequence及Appendable接口为基础,往下做功能扩展,因为String本身是是不可变了,StringBuffer及StringBuilder为了能够更方便、更高效地操作字符串,实现了许多操作字符串的功能,比如追加字符串、插入字符串、替换字符串等等。
按照上面类结构分析StringBuffer主要继承了抽象类AbstractStringBuilder及接口CharSequence,其中AbstractStringBuilder主要提供一些字符串的操作方法,按照功能区分可以划分为追加字符串、删除字符、替换字符、插入字符、获取字符的索引、字符串翻转等,该系列方法都是操作字符串的核心方法,而实现于CharSequence接口的方法主要是获取字符串长度、获取某个索引的字符。
注意:所有的方法都是线程安全的,因为每个方法都用了synchronized关键字修饰,属于一种较为重量级别的锁,按照锁的特性,性能势必会较低。
StringBuffer的整体属性继承于AbstractStringBuilder,而AbstractStringBuilder底层则是用字符数组来存储字符。
char[] value;
int count;
下面按照功能点梳理源码
该方法主要是调用父类的AbstractStringBuilder的构造方法,建立不包含任何字符串的字符串缓冲序列,默认创建的缓冲大小为16
StringBuffer.java
public StringBuffer() {
super(16);
}
AbstractStringBuilder.java
AbstractStringBuilder(int capacity) {
value = new char[capacity];
}
创建一个不包含任何字符串的StringBuffer对象,给定特定的字符串缓冲大小,而通过super()调用的父类方法与上者类似。
public StringBuffer(int capacity) {
super(capacity);
}
将特定的字符串对象构造为StringBuffer对象,默认创建的StringBuffer大小为参数字符串长度+16
public StringBuffer(String str) {
super(str.length() + 16);
append(str);
}
其中通过append()方法将字符串存储到原数组中,注意该方法中加上了synchronized关键字,通过加锁的方式,保证了该方法多线程间的安全性,而该方法内部的实现是通过父类AbstractStringBuilder中的append()方法。从这里也可以看到AbstractStringBuilder的重要性,基本上是StringBuffer类的核心方法的实现。
@Override
public synchronized StringBuffer append(String str) {
toStringCache = null;
super.append(str);
return this;
}
调用父类的append()
// 该方法是父类AbstractStringBuilder的共用方法
public AbstractStringBuilder append(String str) {
if (str == null)
return appendNull();
int len = str.length();
// 用于数组长度的扩展
ensureCapacityInternal(count + len);
// 将字符串str追加到value字符数组后
str.getChars(0, len, value, count);
count += len;
return this;
}
数组扩展的方法分析:
// 数组扩展的方法
// 该方法是ensureCapacity的非同步版本
// 主要目的将数组扩展至minimumCapacity,主要是利用数组复制的方式
private void ensureCapacityInternal(int minimumCapacity) {
if (minimumCapacity - value.length > 0) {
value = Arrays.copyOf(value,newCapacity(minimumCapacity));
}
}
/**
* 扩展数组大小的思路
* 1. 判断参数中的minCapacity和(2*nowCapacity+2) = newCapacity的大小
* 2. 如果newCapacity < minCapacity,则将数组扩展为minCapacity
* 3. 如果newCapacity > minCapacity,则将数组扩展为2 * nowCapacity + 2
* 4. 校验最新扩容的大小,至少不能比Integer.MAX_VALUE大
* @param minCapacity
* @return
*/
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;
}
/**
* 校验最新扩容的大小,防止数据溢出
* @param minCapacity
* @return
*/
private int hugeCapacity(int minCapacity) {
if (Integer.MAX_VALUE - minCapacity < 0) {
throw new OutOfMemoryError();
}
return (minCapacity > MAX_ARRAY_SIZE)
? minCapacity : MAX_ARRAY_SIZE;
}
将字符序列构造成字符串,默认创建的StringBuffer大小为参数的字符串长度+16
具体的数组长度扩展和构造方法3类似
public StringBuffer(CharSequence seq) {
this(seq.length() + 16);
append(seq);
}
基本方法会按照功能来区分各个方法,为了控制分析的简介性,每个功能只分析一个方法。
源代码中提供了多种追加字符串的方式,但是整体大同小异,只是处理请求参数的不同而已,现在只是针对最普遍的请求参数Object作分析
/**
* 追加字符串
* 该方法继承于AbstractStringBuilder
* 思路:
* 1. 将参数做强制类型转化为String,再通过父类AbstractStringBuilder的append()方法追加字符串
* @param obj an {@code Object}.
* @return
*/
@Override
public synchronized StringBuffer append(Object obj) {
toStringCache = null;
// 调用父类的append()方法追加字符串
super.append(String.valueOf(obj));
return this;
}
删除指定索引范围内的字符,还有一个方法是删除指定索引的字符,整体思路一致。
/**
* @param start 开始的下标索引
* @param end 结束的下标索引
* @return
*/
@Override
public synchronized StringBuffer delete(int start, int end) {
toStringCache = null;
// 通过调用父类的删除字符串方法
super.delete(start, end);
return this;
}
/**
* 删除字符序列中一部分
* 思路:
* 1. 前面的都是一些参数的校验,比如检查收尾索引是否得当
* 2. 通过数组复制的方式删除元素
* @param start
* @param end
* @return
*/
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;
}
/**
* 获取字符串的一部分
* @param start 开始索引
* @param end 结束索引
* @return
*/
@Override
public synchronized String substring(int start, int end) {
return super.substring(start, end);
}
/**
* 获取字符串一部分
* 前面是参数的校验,真正的思路是通过String的构造方法,构造出一个新的字符串
* @param start
* @param end
* @return
*/
public String substring(int start, int end) {
if (start < 0)
throw new StringIndexOutOfBoundsException(start);
if (end > count)
throw new StringIndexOutOfBoundsException(end);
if (start > end)
throw new StringIndexOutOfBoundsException(end - start);
return new String(value, start, end - start);
}
虽然方法众多,但是只分析其中一个方法,用一个方法了解整个插入的思路。
/**
* 将字符串插入到元字符串中
* @param offset 开始插入的索引下标
* @param obj 插入的元素
* @return
*/
@Override
public synchronized StringBuffer insert(int offset, Object obj) {
toStringCache = null;
// 调用父类的insert()方法
super.insert(offset, String.valueOf(obj));
return this;
}
/**
* 将字符串插入的原字符索引中
* 思路:
* 1. 扩容
* 2. 复制数组的方式扩展字符数组
* @param offset 开始索引
* @param str 插入的字符串
* @return
*/
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(value, offset);
count += len;
return this;
}
获取str字符串在原字符串中的开始位置,该方法非同步方法。
该方法通过多层调用,StringBuffer ——> AbstractStringBuilder ——> String
@Override
public int indexOf(String str) {
return super.indexOf(str);
}
/**
* 该方法主要用于String、StringBuffer中的搜索使用,
* @param source 被搜索的字符串
* @param sourceOffset 被搜索字符的下标
* @param sourceCount 被搜索字符串的总字符数
* @param target 搜索的字符串
* @param targetOffset 搜索字符串下标
* @param targetCount 搜索字符串总字符数
* @param fromIndex 开始搜索的下标
* @return 返回目标字符串的起始下标,如果没有则返回-1
*/
static int indexOf(char[] source, int sourceOffset, int sourceCount,
char[] target, int targetOffset, int targetCount,
int fromIndex) {
if (fromIndex >= sourceCount) {
return (targetCount == 0 ? sourceCount : -1);
}
if (fromIndex < 0) {
fromIndex = 0;
}
if (targetCount == 0) {
return fromIndex;
}
char first = target[targetOffset];
int max = sourceOffset + (sourceCount - targetCount);
/**
* 整体思路:通过双层循环方式寻找目标字符串的开始及结束
*/
for (int i = sourceOffset + fromIndex; i <= max; i++) {
/* Look for first character. */
if (source[i] != first) {
while (++i <= max && source[i] != first);
}
/* Found first character, now look at the rest of v2 */
if (i <= max) {
int j = i + 1;
int end = j + targetCount - 1;
for (int k = targetOffset + 1; j < end && source[j]
== target[k]; j++, k++);
if (j == end) {
/* Found whole string. */
return i - sourceOffset;
}
}
}
return -1;
}
按照源码分析,该方法本身的时间复杂度为O(log2N)
/**
* 字符串翻转
* @return
*/
@Override
public synchronized StringBuffer reverse() {
toStringCache = null;
// 通过调用父类的字符串翻转方法
super.reverse();
return this;
}
/**
* 翻转字符串
* 整体的时间复杂度:O(log2N)
* @return
*/
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.isSurrogate(cj) ||
Character.isSurrogate(ck)) {
hasSurrogates = true;
}
}
if (hasSurrogates) {
reverseAllValidSurrogatePairs();
}
return this;
}
看到源码中提供了两个方法length()及capacity()方法,前者是获取StringBuffer对象中真正存储的字符数,后者获取的StringBuffer对象的数组的真正的容量。
/**
* 获取StringBuffer对象字符串的总长度
* @return
*/
@Override
public synchronized int length() {
return count;
}
/**
* 表示字符数组中的容量,一般该方法获取的值>=length()获取的值
* @return
*/
@Override
public synchronized int capacity() {
return value.length;
}
例子:
public class TestStringBuffer {
public static void main(String[] args) {
// 该构造方法会默认创建长度为26的字符数组
StringBuffer s = new StringBuffer("helloWorld");
System.out.println(s.length()); // 10
System.out.println(s.capacity()); // 26
}
}
可以从上面方法看到,该类中的方法基本上都加上了Synchronized关键字用于保证多线程间的线程安全。
本文不对源码做详细的分析,因为本身代码和StringBuffer大同小异,只是StringBuilder每个方法没有加上Synchronized,虽然损失了同步的特性,但极大的提升了性能,一般使用的频率也比StringBuffer大。