在学习String类中会触及到StringBulider和StringBuffer,那么这三个类有什么区别呢?
运行速度
在这方面运行速度快慢为: StringBuilder > StringBuffer > String
为什么说String是最慢的呢?
String为字符串常量,而StringBuilder和StringBuffer均为字符串变量,即String对象一旦创建之后该对象是不可更改的,但后两者的对象是变量,是可以更改的。
举个例子来说String慢的原因
如果运行这段代码会发现先输出“abc”,然后又输出“abcde”,好像是a这个对象被更改了,其实,这只是一种假象罢了,JVM对于这几行代码是这样处理的,首先创建一个String对象str,并把“abc”赋值给a,然后在第三行中,其实JVM又创建了一个新的对象也名为a,然后再把原来的a的值和“de”加起来再赋值给新的a,而原来的a就会被JVM的垃圾回收机制(GC)给回收掉了,所以,a实际上并没有被更改,也就是前面说的String对象一旦创建之后就不可更改了。所以,Java中对String对象进行的操作实际上是一个不断创建新的对象并且将旧的对象回收的一个过程,所以执行速度很慢。
用一句话来总结就是:
String创建之后不可更改,String做运算时是创建了一个新的同名的运算,之前的会被删除
而StringBuilder和StringBuffer的对象是变量,对变量进行操作就是直接对该对象进行更改,而不进行创建和回收的操作,所以速度要比String快很多。
当然,有时候我们会这样给String赋值:
它输出的结果为:
这个时候
String a = "abcde"
String a = "abc" + "de"
是完全一样的,所以这个时候String的速度却比StringBuilder的反应速度要快很多。
但是如果写成:
String a = "abc";
a = a + "de";
就会像之前说的一样执行创建删除回收等操作,速度就会慢。
其实StringBuilder和StringBuffer用法都是一样,几乎没啥区别。
它们两个的区别就在于 线程的安全性和效率。如果一个StringBuffer对象在字符串缓冲区被多个线程使用时,StringBuffer中很多方法可以带有 synchronized关键字,所以可以保证线程是安全的,但StringBuilder的方法则没有该关键字,所以不能保证线程安全,有可能会出现一些错误的操作。所以如果要进行的操作是多线程的,那么就要使用StringBuffer,但是在单线程的情况下,还是建议使用速度比较快的StringBuilder。
在StringBuilder的源码中
StringBuilder的定义了char的数组,这个数组长度是可变的,它的长度就是count
char[] value;
int count;
先new个StringBuilder对象
StringBuilder sb = new StringBuilder();
StringBuilder的构造方法,默认就是创建16的字符数组,也就是16个字节的大小
public StringBuilder() {
super(16);
}
public StringBuilder(CharSequence seq) {
this(seq.length() + 16);
append(seq);
}
StringBuilder的append的方法
private StringBuilder append(StringBuilder sb) {
if (sb == null)
return append("null");
int len = sb.length();
int newcount = count + len;
if (newcount > value.length)
expandCapacity(newcount);
sb.getChars(0, len, value, count);
count = newcount;
return this;
}
当append字符串的时候,首先会判断sb是否为空,如果不为空就获取sb的长度和StringBuilder已经拼接的字符串长度之和,也就是newcount,如果newcount大于了字符串数组(value)的长度,那么就使用expandCapacity扩充容量。最后使用sb.getChars()将新添加的字符串添加到字符串数组(value)中,并将newcount的值赋予count。
private void ensureCapacityInternal(int minimumCapacity) {
if (minimumCapacity - value.length > 0)
expandCapacity(minimumCapacity);
}
ensureCapacityInternal方法又调用了expandCapacity方法
oid 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);
}
先扩充16*2+2字节的容量(34字节),然后判断新扩充的字节是否大于StringBuilder的拼接字符串的长度(新添加的+已经有的字符串的长度),如果小于的话,那么新扩充的长度newCapacity就赋予StringBuilder的拼接的字符串的长度。如果newCapacity<0说明oom异常了(内存不够用了),那么这时候newCapacity = Integer.MAX_VALUE。最后在把字符串数组value的内存扩充。
总结: