一、StringBuider和StringBuffer
大家都知道String是不可变类,在内存中有个字符串常量池的地方,顾名思义,这块内存保存着各种字符串,它们是不重复的,一个字符串变量引用着这块内存的某个字符串,因此,当出现不同的字符串时,并不会修改字符串池中已有的字符串,而是新增。
StringBuider和StringBuffer就可以改变字符串而不是不断地新建,这两个的主要区别是StringBuffer使用了synchronized来进行线程同步,因此是线程安全的,适用于多线程。相反,StringBuider更适用于单线程,效率会更高。
二、StringBuider解析
因为StringBuider和StringBuffer的主要区别就是StringBuffer在操作时代码块中多了synchronized关键字,其他和StringBuider差不多,因此,以StringBuider为例,解析下它们的实现原理。
不知道大家在使用时有没有注意到StringBuider的length和capacity返回结果往往不同,查看源码发现,StringBuider有个关键父类AbstractStringBuilder,这个类定义了length和capacity方法:
public int length() {
return count;
}
public int capacity() {
return value.length;
}
先大概说下,value其实就是个char数组,capacity返回的就是这个数组总容量,因为数组不一定是满的,count则是数组里面实际字符数。
下面从它的构造函数开始,讲下有代表性的三种构造函数:
public StringBuilder() {
super(16);
}
public StringBuilder(int capacity) {
super(capacity);
}
public StringBuilder(String str) {
super(str.length() + 16);
append(str);
}
观察可以发现,第一种是无参的,第二种是自定义大小的,第三种是使用一个字符串来创建。
这三个构造函数都会先调用super(int capacity),看下在AbstractStringBuilder中的实现:
AbstractStringBuilder(int capacity) {
value = new char[capacity];
}
很简单,前面说过value是一个char数组,在构造函数中则使用传进来的参数新建value数组。再看下这个参数capacity的来源,也就是上面说的那三种构造函数,第一种是无参的,他会使用16来作为默认数组容量,第二种是自定义容量,第三种则是str.length() + 16,也就是字符串的大小加上16作为value数组的初始容量。
说到这里,我们可以知道,在初始化时,会根据我们的需要新建一个char数组,之后的操作其实就是操作这个数组。
再来看下扩容问题,其实数组也是个对象,一旦创建,它的最大容量就确定了,超过会发出越界异常,因此想扩容的话,只能再新建更大的数组。
下面分析下append方法,看下是怎么实现扩容的:
public AbstractStringBuilder append(String str) {
if (str == null) str = "null";
int len = str.length();
ensureCapacityInternal(count + len);
str.getCharsNoCheck(0, len, value, count);
count += len;
return this;
}
新添加字符串str的长度是len,还记得count是表示当前数组中实际字符数,继续调用ensureCapacityInternal(count + len):
void ensureCapacityInternal(int minimumCapacity) {
if (minimumCapacity - value.length > 0)
expandCapacity(minimumCapacity);
}
minimumCapacity就是count+len,先做个判断,这个判断的意思就是在添加字符串后,总的长度是否超过当前数组的最大容量,如果超过了,继续调用expandCapacity(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);
}
注意第一句话,newCapacity新的容量等于当前容量的两倍再加2,然后进行一个判断,如果newCapacity仍然小于minimumCapacity(前面说的count+len),则newCapacity直接等于minimumCapacity。这句活的意思就是,第一次扩容会直接扩大两倍加2,在之后还需要扩容的话,容量会直接等于实际长度。举个例子,开始容量是16,如果数组实际字符数小于16,调用capacity方法,会一直返回16;当append后如果实际长度达到了17或更大,这时新的容量会变成16*2+2=34,调用capacity方法,会返回34;当继续append,实际长度又超过34后,这时新的容量会等于实际长度,也就是capacity方法和length方法会得到相同结果。继续看最后一行代码,这就是使用newCapacity新建一个数组。
StringBuider的重要知识点就先说到这了,其他一些常用方法其实就是操作数组,需要注意的就是扩容的问题,以及在各个阶段capacity和length方法的返回值区别。欢迎大家交流。