String testString = "a";
for (int i = 0; i < 10; i++) {
testString += "b";
}
StringBuffer sbuf = new StringBuffer();
for (int i = 0; i < 10; i++) { sbuf.append("b"); }
StringBuilder sbud = new StringBuilder();
for (int i = 0; i < 10; i++) { sbud.append("b"); }
利用String的 a+=”b”这种方式来构造字符串在我们平时用的比较多,用起来也很方便,但是总有一些有开发经验的前辈们会说:“尽量少使用这种方式,应该多使用StringBuffer、StringBuilder等方式来构造,这种方式会降低整个程序的性能!”,但是至于为什么使用这种方式会降低程序的性能,我们无从得知。今天我编写这篇博客的目的就是对这三种方式进行深入剖析,让读者真正的了解为什么!
1. String构造方式解析
我们常常会使用String testString = "a"; testString += "b";
这种方式来构造字符串,但是却被告知这种方式会降低性能,这到底是怎么一回事呢?接下来我会通过分析源码的方式给大家仔细分析为什么!
String.java
// 为什么说String不是基本数据类型的原因也可以从这段代码中看出来
// String字符串的实现方式是建立在字符数组之上的
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence {
/** The value is used for character storage. */
/** String的内容是存放在字符数组中的. final意味着字符串的内容是只读的,不可修改的*/
private final char value[];
....
}
大家从这段代码中看到了什么?final关键字,这意味着String从一开始初始化,它的内容是不允许修改的,那么我们平时通过String testString = "a"; testString = "b";
这种方式对字符串重新赋值是怎么做到的呢,既然我们不能修改String的值,那就只能重新创建一个String对象了,然后让testString指向新的String对象的地址即可,这就意味着每一次执行赋值(testString=“a”或者testString += “a”)都会重新创建一个对象,然而创建对象和销毁对象是一个浪费时间和空间的过程,所以有过经验的开发者都会说,尽量不要使用这种方式构造字符串,这也是为什么说这种方式会影响程序性能的原因了。
2. StringBuffer方式的构造
AbstractStringBuilder.java
abstract class AbstractStringBuilder implements Appendable, CharSequence {
/** * The value is used for character storage. */
/* 存放字符串的字符数组 */
char[] value;
......
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;
}
}
StringBuffer.java
// 继承AbstractStringBuilder,所以说也继承了char[] value;
public final class StringBuffer extends AbstractStringBuilder implements java.io.Serializable, CharSequence {
/** * A cache of the last value returned by toString. Cleared * whenever the StringBuffer is modified. */
/* 缓存上一次toString方法返回的值,每次append或者其他修改操作的时候都会清空*/
private transient char[] toStringCache;
......
public synchronized int offsetByCodePoints(int index, int codePointOffset) {
return super.offsetByCodePoints(index, codePointOffset);
}
@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 toString() {
// 生成String对象
if (toStringCache == null) {
toStringCache = Arrays.copyOfRange(value, 0, count);
}
return new String(toStringCache, true);
}
大家从上面的代码有没有发现什么?synchronized关键字、char[] value已经没有用final修饰了。synchronized修饰意味着这是线程安全的,适合多线程使用;value不再使用final修饰意味着每次insert、append只是纯粹的修改了value的值,而没有重新创建新的String对象,减少了对象创建和销毁的过程,更高效。
总结一下两点
1. synchronized修饰,可以多线程使用,但是锁的争用降低效率,如果单线程想要使用此方法构造,同样会存在上锁和释放锁的过程,效率低下。
2. char[] value已经没有用final修饰了,字符串的构造过程只是值的修改过程,没有对象的创建,效率更高。
3. StringBuilder构造方式的剖析
AbstractStringBuilder.java
// 和StringBuffer一样的父类
abstract class AbstractStringBuilder implements Appendable, CharSequence {
/** * The value is used for character storage. */
/* 存放字符串的字符数组 */
char[] value;
......
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;
}
}
StringBuilder.java
// 和StringBuffer一样继承AbstractStringBuilder
// 字符串存放在AbstractStringBuilder的value中
public final class StringBuilder extends AbstractStringBuilder implements java.io.Serializable, CharSequence {
......
@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
return new String(value, 0, count);
}
}
大家可以看到方法修饰少了synchronized关键字,少了toStringCache(并没有什么特别的影响),除此之外和StringBuffer是差不多的。少了synchronized更方便单线程使用,提高了效率。
通过上面我们分别对String、StringBuffer、StringBuilder的源码分析,总结三种方式进行字符串构造的优缺点如下:
1. String字符串构造方式会不断的创建和销毁对象,时间和空间浪费大。
2. StringBuffer方式不会频繁的创建和销毁对象,性能大大提升,而且操作是多线程安全的,所以适合多线程的字符串构造,但是对于单线程来说仍然存在上锁和释放锁的过程,增加了构造的时间,不利于单线程操作。
3. StringBuilder大体上同StringBuffer一样,但是没有加入多线程的安全机制,不适合多线程使用,但是由于没有引入多线程安全的机制,所以去除了上锁和释放锁的过程,减少了构造的时间,适合单线程使用。