讲一下string和stringbuilder,stringbuffer的区别以及capacity的扩增机制——源码分析
操作字符串:string\stringbuilder\stringbuffer
string是内部不可变的字符串,string底层使用了一个不可变的字符数组(final char[])
stringbuilder和stringbuffer是内容可变的字符串,底层没有使用final
stringbuilder和stringbuffer的区别在于stringbuilder是线程不安全的,stringbuffer是线程安全的。
可以看到stringbuilder和stringbuffer通过append(xxx)方法来动态追加字符串,而string则不能。下面通过分析源码来加深自己的认识。(之前都是死记硬背,以后能看源码的自己看源码来分析。)
package csdn.hsy.cool.blog.ms;
public class StrTest {
public static void main(String[] args) {
String strH="haosy";
StringBuilder strS=new StringBuilder("stringbuilderHaosy");
StringBuffer strY=new StringBuffer("stringbufferHaosy");
strY.append("xxx");
strS.append("xxxxx");
System.out.println(strY);
}
}
String strH="haosy";
分析一下这一句,我们都知道,"haosy"其实是 new String(“haosy”);,这个就不多少了吧,那么我们来看一下构造器到底做了什么。
public String(String original) {
this.value = original.value;
this.hash = original.hash;
}
可以看到把值赋给了什么?this.value是什么?跟踪一下
private final char value[];
呼,看到了没?这是一个final,所以你懂了没?string不可变应该明白了吧。
那我们来看一下stringbuffer和stringbuiler到底又是为什么可变的。
先从我们知道的入手,最起码我相信你应该比我聪明把,append()方法最起码你会用把,我们就从它入手看一下,找找看能不能看出什么端倪。
strY.append("xxx");
看下这句,跟踪一下append的实现原理
@Override
public synchronized StringBuffer append(String str) {
toStringCache = null;
super.append(str);
return this;
}
先忽略其它的代码,看 super.append(str);这句,我们跟踪一下父类的方法
public AbstractStringBuilder append(String str) {
if (str == null)
return appendNull();
int len = str.length();
ensureCapacityInternal(count + len);
str.getChars(0, len, value, count);
count += len;
return this;
}
同样,先忽略其它代码,遵循先深度后广度的原则跟踪分析一下代码
看这句str.getChars(0, len, value, count);
其实这句代码的意思你应该明白,getChars()方法
public void getChars(int srcBegin,
int srcEnd,
char[] dst,
int dstBegin)
将此字符串中的字符复制到目标字符数组中
参数
srcBegin - 要复制的字符串中第一个字符的索引。
srcEnd - 要复制的字符串中最后一个字符后面的索引。
dst - 目标数组。
dstBegin - 目标数组中的起始偏移量。
如果实在是不知道,源码奉上。
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);
}
System.arraycopy(value, srcBegin, dst, dstBegin, srcEnd - srcBegin);
}
我想让你知道的是,第3个参数是一个字符数组,看这句str.getChars(0, len, value, count);中的value要知道是一个数组,跟踪一下。
char[] value;
看到了把,没有final,所以可变,明白了吧。可能你还不明白,迷迷糊糊,下面深入分析一下append方法,刚才我们忽略的代码下面我们来刨析。
特别注意:源码主要分析了StringBuffer,Stringbuilder没有分析,因为比较相似,如果有兴趣,请自行分析,也相信你会很有用兴趣。
======================================================================================
append()方法深入分析
@Override
public synchronized StringBuffer append(String str) {
toStringCache = null;
super.append(str);
return this;
}
首先可以看到synchronized,线程安全的,这个stringbuilder是有所不同的,来看一下
@Override
public StringBuilder append(String str) {
super.append(str);
return this;
}
所以,你要问我StringBuffer和StringBuilder有什么区别?我回答StringBuffer是线程安全的,但是效率会低,StringBuilder是非线程安全的,所以效率高。好,一笔带过,让我们下文不要再分析StringBuilder了好么?留给你自己吧~
回来老弟
@Override
public synchronized StringBuffer append(String str) {
toStringCache = null;
super.append(str);
return this;
}
toStringCache = null;
这是做什么的?定位了整个StringBuffer
@Override
public synchronized String toString() {
if (toStringCache == null) {
toStringCache = Arrays.copyOfRange(value, 0, count);
}
return new String(toStringCache, true);
}
======================================================================
/**
* A cache of the last value returned by toString. Cleared
* whenever the StringBuffer is modified.
*/
private transient char[] toStringCache;
看到这句注释返回最后一次toString的缓存值,一旦StringBuffer被修改就清除这个缓存值。
想了半天想不明白到底是做什么的.但是知道这是一个缓存相关的,然后我去StringBuilder中看了一眼竟然没有这个,霍,我懂了,你呢?
StringBuffer线程安全,到处synchronized关键字,性能与StringBuilder相比大打折扣,线程安全应用场景多线程,访问的数量级肯定比单线程环境要大的多,所以弄个缓存,可以平衡StringBuffer的性能。
回来老弟
@Override
public synchronized StringBuffer append(String str) {
toStringCache = null;
super.append(str);
return this;
}
super.append(str);
调用父类的append方法啊,最后再返回this关键字。去父类append方法中转悠一圈吧。
public AbstractStringBuilder append(String str) {
if (str == null)
return appendNull();
int len = str.length();
ensureCapacityInternal(count + len);
str.getChars(0, len, value, count);
count += len;
return this;
}
if (str == null)
return appendNull();
一看就知道了吧,追加了个空气,奉上源码appendNull(),里面的ensureCapacityInternal我们先放它一马,跑不掉的。
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;
}
老弟回来
public AbstractStringBuilder append(String str) {
if (str == null)
return appendNull();
int len = str.length();
ensureCapacityInternal(count + len);
str.getChars(0, len, value, count);
count += len;
return this;
}
int len = str.length();
ensureCapacityInternal(count + len);
看下这,得到追加字符串的长度len,然后调用ensureCapacityInternal方法,确保内部容量?晕乎,看不懂,跟踪一下
private void ensureCapacityInternal(int minimumCapacity) {
// overflow-conscious code
if (minimumCapacity - value.length > 0) {
value = Arrays.copyOf(value,
newCapacity(minimumCapacity));
}
}
得,引出了stringbuffer得扩充机制了。对不起,我的错,来分析一波吧
看到一个新的方法newCapacity(),上源码
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;
}
int newCapacity = (value.length << 1) + 2; << 左移,不好理解,说简单点
int newCapacity = (value.length)*2 + 2;
扩充?如何扩充?放大两倍再加2
好
if (newCapacity - minCapacity < 0) {
newCapacity = minCapacity;
}
如果如果minimumCapacity大于原容量*2+2,那么新容量就取值为minimumCapacity,反之扩充就采用2倍+2原则,相信这个结论你之前都听过,现在应该明白了吧
上代码看看是否是这样的?
System.out.println("扩充机制");
StringBuffer sb1 = new StringBuffer();
for (int num = 0; num < 5; num++) {
sb1.append("12345678");
System.out.println(" this capacity: " + sb1.capacity());
System.out.println(" this length: " + sb1.length());
System.out.println("---------------------------------");
}
结果
扩充机制
this capacity: 16
this length: 8
---------------------------------
this capacity: 16
this length: 16
---------------------------------
this capacity: 34
this length: 24
---------------------------------
this capacity: 34
this length: 32
---------------------------------
this capacity: 70
this length: 40
---------------------------------
说明我们分析的没毛病
扩充机制到此结束了么?并没有
看上面的结果并不是每次都扩充,只有大于原本的容量才扩充,这又是为什么呢?附源码
private void ensureCapacityInternal(int minimumCapacity) {
// overflow-conscious code
if (minimumCapacity - value.length > 0) {
value = Arrays.copyOf(value,
newCapacity(minimumCapacity));
}
}
void expandCapacity(int minimumCapacity) {
int newCapacity = value.length * 2 + 2;
if (newCapacity - minimumCapacity < 0)
newCapacity = minimumCapacity;
if (newCapacity < 0) {
if (minimumCapacity < 0) // overflow
throw new OutOfMemoryError();
newCapacity = Integer.MAX_VALUE;
}
value = Arrays.copyOf(value, newCapacity);
}
综上:扩充机制?
1、小于当前容量时,容量不变。
本例中,容量依然为16。
2、大于当前容量,并且小于(当前容量+1)*2,则容量变为(当前容量+1)*2。
本例中,16<20<(16+1)*2=34,所以容量为34。
3、大于当前容量,并且大于(当前容量+1)*2,则容量变为用户所设置的容量。
本例中,80>16,80>(16+1)*2=34,所以容量为80。
好,兄弟,回来,扩充机制只是迫不得已分析的,我们还是要分析append()方法的
public AbstractStringBuilder append(String str) {
if (str == null)
return appendNull();
int len = str.length();
ensureCapacityInternal(count + len);
str.getChars(0, len, value, count);
count += len;
return this;
}
str.getChars(0, len, value, count);
把结果放入value中
count += len;
记录当前的长度,用于后续扩充的判断条件
return this;
返回,目前在AbstractStringBuilder这个父类中,回溯到调用出StringBuffer的append方法
@Override
public synchronized StringBuffer append(String str) {
toStringCache = null;
super.append(str);
return this;
}
回溯,返回最终的结果。
源码分析:一刷 2020/08/25 水平有限,分析难免会有误差,有问题请及时指出。