深入源码分析StringBuffer和StringBuilder

深入源码分析StringBuffer和StringBuilder

众所周知,StringBuffer是线程安全,StringBuilder线程不安全,所以StringBuilder性能略高,那还有没有其他细节上的特性呢?让我们从源码分析

StringBuffer和StringBuilder都继承了AbstractStringBuilder类

AbstractStringBuilder类关键源码

  • 抽象类,是Stringbuilder和StringBuffer的父类,有两个主要的属性:
    • 字符数组value,用来存放字符串
    • int类型的count值,用于计算存储使用的字符数量,并且每次增删改都会更新该count值
    • capacity方法才是返回字符数组的总长度,value.length;
  • 该类中的大部分方法被其子类StringBuffer和StringBuilder所调用,其中的扩容机制是关键
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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扩容机制

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
//扩容机制 确保容量
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);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
  
//用于保留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方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
//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方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
//删除一部分的字符,传入要删除段的开始下标和终止下标
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方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
//在对象中间插入字符串数组
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方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
//反转字符串
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方法

1
2
3
4
5
6
7
8
9
10
11
12
    //返回一个新字符串,是此字符串的一个子字符串
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() 源码如下:

1
2
3
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,加上一点扩容机制
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
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方法时起到提高效率的作用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
 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对象,会在堆内存中产生很多垃圾;
    • 虽然最终调用的是系统复制数组操作,但调用之前开销非常大,只能靠复制来解决拼接问题
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
 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线程不安全

使用总结

  1. 如果要操作少量的String数据可以使用用 String

  2. 不考虑线程安全问题,大量数据进行操作使用 StringBuilder

  3. 考虑线程安全问题,大量数据进行操作使用 StringBuffer