深入源码分析StringBuffer和StringBuilder
众所周知,StringBuffer是线程安全,StringBuilder线程不安全,所以StringBuilder性能略高,那还有没有其他细节上的特性呢?让我们从源码分析
StringBuffer和StringBuilder都继承了AbstractStringBuilder类
AbstractStringBuilder类关键源码
- 抽象类,是Stringbuilder和StringBuffer的父类,有两个主要的属性:
- 字符数组value,用来存放字符串
- int类型的count值,用于
计算存储使用的字符数量
,并且每次增删改都会更新该count值 - capacity方法才是返回字符数组的总长度,value.length;
- 该类中的大部分方法被其子类StringBuffer和StringBuilder所调用,其中的扩容机制是关键
abstract class AbstractStringBuilder implements Appendable, CharSequence {
//用来存字符串
char[] value;
//用来计算存储使用的字符数量
int count;
public int length() {
//注意,这里的length方法返回的是count的值,而不是value.length,
//区别于String类中的length方法
return count;
}
//capacity()才是返回字符数组的总长度
public int capacity() {
return value.length;
}
ensureCapacity扩容机制
//扩容机制 确保容量
public void ensureCapacity(int minimumCapacity) {
// 最小容量肯定大于0,严谨性判断
if (minimumCapacity > 0)
// 一层一层环环相扣的封装
ensureCapacityInternal(minimumCapacity);
}
private void ensureCapacityInternal(int minimumCapacity) {
//如果最小容量大于内置的数组长度就必须要扩容了
if (minimumCapacity - value.length > 0)
expandCapacity(minimumCapacity);
}
//开始扩容
void expandCapacity(int minimumCapacity) {
//自动扩容机制,每次扩容(value.length+1)*2
int newCapacity = value.length * 2 + 2;
//如果扩容后的容量还是小于传入的参数,就直接将传入的参数设为容量值
if (newCapacity - minimumCapacity < 0)
newCapacity = minimumCapacity;
//如果传入的参数小于0,则直接把容量设置到Integer的最大值 2^31-1
if (newCapacity < 0) {
if (minimumCapacity < 0) // overflow
throw new OutOfMemoryError();
newCapacity = Integer.MAX_VALUE;
}
//还是使用了Arrays.copyOf函数直接返回一个新容量的数组
value = Arrays.copyOf(value, newCapacity);
}
//用于保留value的值,保留的长度为count的值,只有当count的值小于value.length时起作用
public void trimToSize() {
//当count小于value.length时,将value多余长度的值删除,
//此时value.length的长度等于count
if (count < value.length) {
value = Arrays.copyOf(value, count);
}
}
// 设置长度length
public void setLength(int newLength) {
if (newLength < 0)
throw new StringIndexOutOfBoundsException(newLength);
//当传入的值大于等于0时,进行扩容
ensureCapacityInternal(newLength);
//当传入值大于字符统计量
if (count < newLength) {
//为新扩容的数组中的新元素填充'\0',同样也是结束符 从count位置到newLength位置
Arrays.fill(value, count, newLength, '\0');
}
//最后设置新的字符长度量
count = newLength;
}
//append方法使用到的一个方法,用以添加一个字符串数组
public void getChars(int srcBegin, int srcEnd, char[] dst, int dstBegin)
{
if (srcBegin < 0)
throw new StringIndexOutOfBoundsException(srcBegin);
if ((srcEnd < 0) || (srcEnd > count))
throw new StringIndexOutOfBoundsException(srcEnd);
if (srcBegin > srcEnd)
throw new StringIndexOutOfBoundsException("srcBegin > srcEnd");
//用于添加字符串,将value的值添加到dst[]中
System.arraycopy(value, srcBegin, dst, dstBegin, srcEnd - srcBegin);
}
append方法
//append方法的重载
//在对象后拼接字符串或其他对象,效率相比String较高,
//因其在拼接时并没有创建新的对象,不会造成大量对象的堆积,性能提升相当明显
public AbstractStringBuilder append(Object obj) {
return append(String.valueOf(obj));
}
public AbstractStringBuilder append(String str) {
if (str == null)
//若传入的字符串为null,则默认添加“null”这个字符串
return appendNull();
int len = str.length();
//注意扩容,直接是计算字符量的长度,根据长度就扩容到这么多
ensureCapacityInternal(count + len);
//用getChars方法去添加字符串数组
str.getChars(0, len, value, count);
//更新count字符量值,每次添加就会加上新加入的字符串长度值
count += len;
return this;
}
//拼接StringBuffer对象,方法同理
public AbstractStringBuilder append(StringBuffer sb) {
if (sb == null)
return appendNull();
int len = sb.length();
ensureCapacityInternal(count + len);
sb.getChars(0, len, value, count);
count += len;
return this;
}
//同样也可以拼接AbstractStringBuilder对象,也可以是其实现类,例如StringBuilder
AbstractStringBuilder append(AbstractStringBuilder asb) {
if (asb == null)
return appendNull();
int len = asb.length();
ensureCapacityInternal(count + len);
asb.getChars(0, len, value, count);
count += len;
return this;
}
//对象为null时,就添加个“null”字符串
private AbstractStringBuilder appendNull() {
int c = count;
ensureCapacityInternal(c + 4);
final char[] value = this.value;
value[c++] = 'n';
value[c++] = 'u';
value[c++] = 'l';
value[c++] = 'l';
count = c;
return this;
}
//添加int类型的值
public AbstractStringBuilder append(int i) {
//细节,当int值是最小值时,特殊处理
if (i == Integer.MIN_VALUE) {
append("-2147483648");
return this;
}
//细节,判断Integer的位数,负数有负号,要多加一位
int appendedLength = (i < 0) ? Integer.stringSize(-i) + 1
: Integer.stringSize(i);
//根据新传入的int值,确定总体字符串长度值,根据此时长度值进行扩容
int spaceNeeded = count + appendedLength;
//扩容
ensureCapacityInternal(spaceNeeded);
//扩容之后就是添加值了,int值用Integer的静态方法
Integer.getChars(i, spaceNeeded, value);
//更新总体的字符数组长度值
count = spaceNeeded;
return this;
}
//添加long类型的值,原理和int类似
public AbstractStringBuilder append(long l) {
if (l == Long.MIN_VALUE) {
append("-9223372036854775808");
return this;
}
int appendedLength = (l < 0) ? Long.stringSize(-l) + 1
: Long.stringSize(l);
int spaceNeeded = count + appendedLength;
ensureCapacityInternal(spaceNeeded);
Long.getChars(l, spaceNeeded, value);
count = spaceNeeded;
return this;
}
//浮点类型,直接调用对应的包装类中的方法
public AbstractStringBuilder append(float f) {
FloatingDecimal.appendTo(f,this);
return this;
}
public AbstractStringBuilder append(double d) {
FloatingDecimal.appendTo(d,this);
return this;
}
//添加Boolean类型 简单粗暴
public AbstractStringBuilder append(boolean b) {
if (b) {
ensureCapacityInternal(count + 4);
value[count++] = 't';
value[count++] = 'r';
value[count++] = 'u';
value[count++] = 'e';
} else {
ensureCapacityInternal(count + 5);
value[count++] = 'f';
value[count++] = 'a';
value[count++] = 'l';
value[count++] = 's';
value[count++] = 'e';
}
return this;
}
delete方法
//删除一部分的字符,传入要删除段的开始下标和终止下标
public AbstractStringBuilder delete(int start, int end) {
//验证参数的有效性
if (start < 0)
throw new StringIndexOutOfBoundsException(start);
if (end > count)
//结束下标大于count时,将count设为结束下标
end = count;
if (start > end)
//开始下标就大于结束下标
throw new StringIndexOutOfBoundsException();
//要删除的长度值
int len = end - start;
if (len > 0) {
//System的静态方法来实现数组之间的复制。
//src:源数组;
//srcPos:源数组要复制的起始位置;
//dest:目的数组;
//destPos:目的数组放置的起始位置;
//length:复制的长度。注意:src and dest都必须是同类型或者可以进行转换类型的数组.
//System.arraycopy函数可以实现自己到自己复制
//将中间要删除段省略,不进行复制
System.arraycopy(value, start+len, value, start, count-end);
//更新count大小
count -= len;
}
return this;
}
insert方法
//在对象中间插入字符串数组
public AbstractStringBuilder insert(int dstOffset, CharSequence s) {
if (s == null)
s = "null";
//明确了是String类型,会发生强制转换
if (s instanceof String)
return this.insert(dstOffset, (String)s);
return this.insert(dstOffset, s, 0, s.length());
}
//传入的是插入开始下标,以及要插入的字符串
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);//先扩容
//再复制数组,注意,此时只是在value中建立起用于存放插入值的空位
System.arraycopy(value, offset, value, offset + len, count - offset);
//向空位中插入str
str.getChars(value, offset);
//更新count值
count += len;
return this;
}
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.isSurrogate(cj) ||
Character.isSurrogate(ck)) {
hasSurrogates = true;
}
}
if (hasSurrogates) {
//直接反转后,如果两字符顺序错了,就需要重新调整。
//考虑到存在增补字符,需要成对校验,就是超出了字符的编码范围的话就会重新翻转到原来那样
reverseAllValidSurrogatePairs();
}
return this;
}
//reverse的依赖方法,重新调整字符顺序
private void reverseAllValidSurrogatePairs() {
for (int i = 0; i < count - 1; i++) {
char c2 = value[i];
if (Character.isLowSurrogate(c2)) {
char c1 = value[i + 1];
if (Character.isHighSurrogate(c1)) {
value[i++] = c1;
value[i] = c2;
}
}
}
}
substring方法
//返回一个新字符串,是此字符串的一个子字符串
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);
}
}
System.arraycopy
Arrays.copyOf()方法返回的数组是新的数组对象,数组拷贝时调用的是本地方法 System.arraycopy() ,原数组对象仍是原数组对象,不变,该拷贝不会影响原来的数组。
System.arraycopy() 源码如下:
public static native void arraycopy(Object src, int srcPos,
Object dest, int destPos,
int length);
参数说明:
src:源对象
srcPos:源数组中的起始位置
dest:目标数组对象
destPos:目标数据中的起始位置
length:要拷贝的数组元素的数量
StringBuilder的关键源码
- 线程不安全,修改的方法全部都为线程不安全,是牺牲了安全用以实现性能
- 由final修饰,不能被继承,并且继承了AbstractStringBuilder类
- 重写了toString方法
- 实现了序列化接口,可序列化
- 默认初始化容量为capacity = 16 ,基本所有jdk中实现类涉及初始化容量的大小都为16,加上一点扩容机制
public final class StringBuilder
extends AbstractStringBuilder
implements java.io.Serializable, CharSequence{
//对象序列化和反序列化需要的唯一标识版本号
static final long serialVersionUID = 4383685877147921099L;
//默认构造方法
public StringBuilder() {
//使用父类的构造方法,默认初始化容量为capacity = 16
super(16);
}
//带一个参数的构造方法,可以指定初始化容量
public StringBuilder(int capacity) {
super(capacity);
}
//带一个参数构造方法,与前一个不同,这是指定一个String来初始化
public StringBuffer(String str) {
//注意,String初始化StringBuffer的时候,指定容量大小为String的长度加上16
super(str.length() + 16);
//追加到value中
append(str);
}
@Override
public StringBuilder append(Object obj) {
return append(String.valueOf(obj));
}
@Override
public StringBuilder append(String str) {
super.append(str);
return this;
}
@Override
public String toString() {
// Create a copy, don't share the array
// 注意,此时是new了一个String对象,返回了该String对象
return new String(value, 0, count);
}
//将对象序列化,写入了count和value。
private void writeObject(java.io.ObjectOutputStream s)
throws java.io.IOException {
s.defaultWriteObject();
s.writeInt(count);
s.writeObject(value);
}
//用于反序列化,将count和value属性读取出来
private void readObject(java.io.ObjectInputStream s)
throws java.io.IOException, ClassNotFoundException {
s.defaultReadObject();
count = s.readInt();
value = (char[]) s.readObject();
}
}
StringBuffer的关键源码
一个字符序列可变的字符串,这个StringBuffer提供了一系列的修改方法去改变字符串对象序列
- 线程安全,增删改操作方法都加了
synchronized
锁,加锁系统开销大,效率相对StringBuilder较低 - 注意:和Builder的区别有一个
transient char[] toStringCache
,toStringCache的字符数组,最后一次修改后的缓存值(字符数组保存),只要修改了value,那么就会重置,当然这个Cache是建立在线程安全之上
的,不保证线程安全就不会涉及到该Cache的操作,该toStringCache会在使用toString方法时起到提高效率的作用
public final class StringBuffer
extends AbstractStringBuilder
implements java.io.Serializable, CharSequence
{
// 注意:此属性Builder中没有,意图是最后一次修改后的缓存值(字符数组保存),
// 只要修改了value,那么就会重置
// transient 用于指定哪个字段不被默认序列化
private transient char[] toStringCache;
static final long serialVersionUID = 3388685877147921107L;
public StringBuffer() {
super(16);
}
public StringBuffer(int capacity) {
super(capacity);
}
// 获取字符串字符数量,加synchronized锁
@Override
public synchronized int length() {
return count;
}
// 获取容量,效率低--对象锁
@Override
public synchronized int capacity() {
return value.length;
}
// 确保容量
@Override
public synchronized void ensureCapacity(int minimumCapacity) {
if (minimumCapacity > value.length) {
// 当最小容量值(传进来的参数值)大于value.length(这个其实就是容量),那么就直接扩容
expandCapacity(minimumCapacity);
}
}
//扩容,和AbstractStringBuilder父类中方法一致
void expandCapacity(int minimumCapacity) {
int newCapacity = value.length * 2 + 2;
if (newCapacity - minimumCapacity < 0)
newCapacity = minimumCapacity;
if (newCapacity < 0) {
if (minimumCapacity < 0)
throw new OutOfMemoryError();
newCapacity = Integer.MAX_VALUE;
}
value = Arrays.copyOf(value, newCapacity);
}
@Override
public synchronized void trimToSize() {
//直接调用父类的方法,但线程安全
super.trimToSize();
}
//也是线程安全
@Override
public synchronized void setLength(int newLength) {
toStringCache = null;
//调用父类函数,扩充字符串容量到newLength,并且用空格填充
super.setLength(newLength);
}
//根据指定索引获取字符,效率慢,加锁
@Override
public synchronized char charAt(int index) {
if ((index < 0) || (index >= count))
throw new StringIndexOutOfBoundsException(index);
return value[index];
}
//根据索引修改字符串中某个字符值
@Override
public synchronized void setCharAt(int index, char ch) {
if ((index < 0) || (index >= count))
throw new StringIndexOutOfBoundsException(index);
//注意清除了缓存,只要修改了value,此值就会置空
toStringCache = null;
value[index] = ch;
}
//区别在于加锁了以及清除了缓存,只要修改了value,此值就会置空
@Override
public synchronized StringBuffer append(Object obj) {
toStringCache = null;
super.append(String.valueOf(obj));
return this;
}
@Override
public synchronized StringBuffer append(String str) {
toStringCache = null;
super.append(str);
return this;
}
//不存在内存泄漏,实现了线程安全
@Override
public synchronized String substring(int start) {
return substring(start, count);
}
@Override
public synchronized String substring(int start, int end) {
return super.substring(start, end);
}
//有一套这样的insert方法,分为同步与不同步方法
//只有在参数为char类型时,才是同步方法
@Override
public StringBuffer insert(int offset, boolean b) {
//此方法不同步, 而且也没有 toStringCache = null;
super.insert(offset, b);
return this;
}
@Override
public synchronized StringBuffer insert(int offset, char c) {
toStringCache = null;
super.insert(offset, c);
return this;
}
@Override
public synchronized String toString() {
//toStringCache是提高toString方法的效率,不用每次都是调用,做了一个缓存
if (toStringCache == null) {
toStringCache = Arrays.copyOfRange(value, 0, count);
}
//直接使用的toStringCache数组缓存,提高了效率
return new String(toStringCache, true);//返回一个新的string对象过去
}
// 自定义序列化字段
//serialPersistentFields 用于指定哪些字段需要被默认序列化.如下:
private static final java.io.ObjectStreamField[] serialPersistentFields =
{
new java.io.ObjectStreamField("value", char[].class),
new java.io.ObjectStreamField("count", Integer.TYPE),
new java.io.ObjectStreamField("shared", Boolean.TYPE),
};
// 序列化大到ObjectOutputStream,写入了count和value、shared
private synchronized void writeObject(java.io.ObjectOutputStream s)
throws java.io.IOException {
java.io.ObjectOutputStream.PutField fields = s.putFields();
fields.put("value", value);
fields.put("count", count);
fields.put("shared", false);//默认shared为false
s.writeFields();
}
//反序列化到对象,读出count和value
private void readObject(java.io.ObjectInputStream s)
throws java.io.IOException, ClassNotFoundException {
java.io.ObjectInputStream.GetField fields = s.readFields();
value = (char[])fields.get("value", null);
count = fields.get("count", 0);
}
}
字符串系列类的一些问题
效率问题
效率:StringBuilder > StringBuffer > String
- StringBuffer和StringBuilder都有
自动扩容
机制,增删改操作时,返回的是原来自身的对象,不会重新创建对象,不会产生很多对象垃圾 - String有两大缺点:
- 返回对象使用大量new操作,返回值均为新建的String对象,会在堆内存中产生很多垃圾;
- 虽然最终调用的是系统复制数组操作,但调用之前开销非常大,只能靠复制来解决拼接问题。
public static void main(String[] args) {
long start = System.currentTimeMillis();
String str = null;
for (int i = 0; i < 20000; i++) {
str = str + i + ",";
}
System.out.println("String耗时 "+ (System.currentTimeMillis() - start));
System.out.println("-------------------");
start = System.currentTimeMillis();
StringBuffer buffer = new StringBuffer();
for (int i = 0; i < 20000; i++) {
buffer.append(i + ",");//线程安全所以慢一点,但前提这里是单线程
}
System.out.println("StringBuffer耗时 "+(System.currentTimeMillis() - start));
System.out.println("-------------------");
start = System.currentTimeMillis();
StringBuilder builder = new StringBuilder();
for (int i = 0; i < 20000; i++) {
builder.append(i + ",");//线程不安全,效率最高
}
System.out.println("StringBuilder耗时 "+(System.currentTimeMillis() - start));
}
/*
String耗时 2030
-------------------
StringBuffer耗时 5
-------------------
StringBuilder耗时 3
*
线程安全问题
- 增删改操作时,StringBuffer线程安全,StringBuilder线程不安全
使用总结
-
如果要操作少量的String数据可以使用用 String
-
不考虑线程安全问题,大量数据进行操作使用 StringBuilder
-
考虑线程安全问题,大量数据进行操作使用 StringBuffer