为什么要使用StringBuilder和StringBuffer类,明明它们提供的方法功能都差不多,字符串分割,字符串查找,字符串拼接,等等,能用SringBuilder和StringBuffer类实现的功能,String类也都能实现,那为什么还要使用他们呢?
首先我们先来介绍String类
java中的string的值是通过char[]数组来存储的
private final char[] value;
注意观察,我们可以发现 这个用于存储字符串值的字符数组 value 被final关键词修饰,final表示最终的也就是一旦赋值就不可变的意思,当我们新建一个String类型的实例时,String的构造方法就会为生成一个char[]数组,并把这个数组的地址永久的付给赋给value,直至这个String实例在内存中被销毁。例如,我们新建一个内容为”你好“的String实例
String str=new String("你好");
new String("你好")会调用String的有参构造方法生成一个内容为{'你','好'}的char[]数组并把这个数组的地址给value,当这个String实例被创建好后再把其地址赋给str。
因为value的值不能更改,所以在它的生命周期里它只能指向{'你','好'}这个char[]数组,而数组一但创建就无法再更改它的长度,因此只要str还指向这个String实例,它的字符串长度就无法更改。
但对数组有些了解的人就会产生新的疑问,数组虽然无法改变长度,但却可以改变数组中的元素,既然字符串的内容是由char[]数组存储的,那我们的字符串的内容不应该也是可变的么?
观察String类中的成员方法我们可以发现,String中返回类型为String的方法,返回的String实例都是都过new String(实参列表)得到的新字符串实例,也就是说String类提供给我的方法并不是“按我们需求对字符串操作”的方法,而是“创建符合我们需求字符串”的方法。
那么这时又有人会说,只要满足我们的需求,我们又何必去在意它底层是如何运行的呢,因为众所周知“封装”就是使Java与众不同的原因之一。
No, things are far from that simple!!!
public static void main(String[] args) {
String str="";
for(int i=0;i<=100;i++) {
str=str.concat(String.valueOf(i)).concat(" ");
}System.out.println(str);
}
现在我们想要实现1到100所有整数的字符串拼接
concat()使String类提供我们的字符串拼接方法
valueOf()使String提供给我们将其他类型转化成字符串的静态方法
public String concat(String str) {
if (str.isEmpty()) {
return this;
}
return StringConcatHelper.simpleConcat(this, str);
}
concat()的返回 StringConcatHelper.simpleConcat(this, str);
StringConcatHelper.simpleConcat()方法
static String simpleConcat(Object first, Object second) {
String s1 = stringOf(first);
String s2 = stringOf(second);
if (s1.isEmpty()) {
// newly created string required, see JLS 15.18.1
return new String(s2);
}
if (s2.isEmpty()) {
// newly created string required, see JLS 15.18.1
return new String(s1);
}
// start "mixing" in length and coder or arguments, order is not
// important
long indexCoder = mix(initialCoder(), s1);
indexCoder = mix(indexCoder, s2);
byte[] buf = newArray(indexCoder);
// prepend each argument in reverse order, since we prepending
// from the end of the byte array
indexCoder = prepend(indexCoder, buf, s2);
indexCoder = prepend(indexCoder, buf, s1);
return newString(buf, indexCoder);
}
StringConcatHelper.simpleConcat()方法返回 newString(buf, indexCoder)
newString()方法
static String newString(byte[] buf, long indexCoder) {
// Use the private, non-copying constructor (unsafe!)
if (indexCoder == LATIN1) {
return new String(buf, String.LATIN1);
} else if (indexCoder == UTF16) {
return new String(buf, String.UTF16);
} else {
throw new InternalError("Storage is not completely initialized, " + (int)indexCoder + " bytes left");
}
}
过程虽然曲折,但其实我们只用看newString()方法中的返回值
return new String(buf, String.LATIN1);
return new String(buf, String.UTF16);
new String()!!!是不是很眼熟,String的构造方法,也就是说,concat()会新建一个String实例
这就表明我们每次拼接都会产生一个新的String实例,但我们需要的只有对后一次拼接产生的String实例,而之前产生的String实例不会直接消失,当我们循环拼接的次数特别大时,就会导致内存空间被大量浪费。
看到这里还觉得无所谓么
为了解决这种情况,我们的StringBuilder和StringBuffer类就出现了
这里以StringBuilder为例
StringBuilder继承AbstractStringBuilder, 虽然AbstractStringBuilder也是用char[]字符串存String的内容但他的数组并没用用final修饰,并且它还有一个非常重要的byte变量count用于表示有效长度
char[] value;
byte coder;
String中的char[]的长度和内容的字符数相等,并且因为value被final修饰导致无法指向一个新的数组。
但AbstractStringBuilder中允许char[]的长度大于内容的字符书,在实例化StringBuilder对象时,会先创建一定长度的char[]数组,并给count初始值为0,表示有效长度为0,当我们拼接字符串时,每次往value中存一个字符,count加1,表示有效长度加1,当value指向的char[]被加满是时,就会新建更大的char[]数组复制之前char[]的内容并将复制到新数组中,并将新数组的地址赋给value从而达到了不定长的效果。
因为有count记录有效长度,所以允许数组长度大于实际存入字符数,当我们进行删除,拼接,
替换,我们并不需要考虑长度不可变和有效内容到哪里结束的问题。
并且因为数组长度允许有空余,所以并不是每次拼接字符串时都新建一次数组,而是在不够存的时候才去创建一个长度为原来2倍+2的新数组,这样就会减少创建数组的次数,从而减少运行过程中产生的中间产物。
StringBuilder因为在count有效长度的帮助下,能够在对自己进行添加,删除,替换,后仍能确定有效内容到哪里结束,使之可以对自己的长度内容自由修改。因此StringBuilder提供的方法也是“按我们意愿对自身进行修改”的方法。
StringBuilder和StringBuffer都是继承AbstractStringBuilder,因此他们底层存储的方式是一样的,
不同的是
StringBuffer线程安全(现在很少使用),性能较差;StringBuilder线程不安全,但性能较好