常见的四种字符串拼接方法分别是
1.直接用“+”号
2.使用String的方法concat
3.使用StringBuilder的append
4.使用StringBuffer的append
字符串拼接我分为了两种拼接方式,一种是同一个字符串大量循环拼接,第二种是大量不同的字符串每次都拼接固定数量的字符串。第二种更接近与服务器上的使用场景,因为工程上是很少有一个字符串不断拼接的,基本都是大量字符串按同一种模式拼接(比如构造上报参数)。代码如下:
public class StringConcat {
public static void main(String[] args) {
plus();
concat();
stringBuffer();
stringBuilder();
}
public static void plus(){
String initial = "";
long start = System.currentTimeMillis();
for(int i = 0; i < 100000; i++){
initial = initial + "a";
}
long end = System.currentTimeMillis();
System.out.println("plus:" + (end - start));
start = System.currentTimeMillis();
for(int i = 0; i < 100000; i++){
String a = "a";
a = a + String.valueOf(i);
//a = a + String.valueOf(i) + String.valueOf(i) + String.valueOf(i) + String.valueOf(i) + String.valueOf(i) + String.valueOf(i) + String.valueOf(i) + String.valueOf(i);
}
end = System.currentTimeMillis();
System.out.println("double plus:" + (end - start));
}
public static void stringBuilder(){
StringBuilder initial = new StringBuilder("");
long start = System.currentTimeMillis();
for(int i = 0; i < 100000; i++){
initial = initial.append("b");
}
long end = System.currentTimeMillis();
System.out.println("StringBuilder:" + (end - start));
start = System.currentTimeMillis();
for(int i = 0; i < 100000; i++){
StringBuilder b = new StringBuilder("b");
b.append(String.valueOf(i))
//b.append(String.valueOf(i)).append(String.valueOf(i)).append(String.valueOf(i)).append(String.valueOf(i)).append(String.valueOf(i)).append(String.valueOf(i)).append(String.valueOf(i)).append(String.valueOf(i));
}
end = System.currentTimeMillis();
System.out.println("double StringBuilder:" + (end - start));
}
public static void stringBuffer(){
StringBuffer initial = new StringBuffer("");
long start = System.currentTimeMillis();
for(int i = 0; i < 100000; i++){
initial = initial.append("c");
}
long end = System.currentTimeMillis();
System.out.println("StringBuffer:" + (end - start));
start = System.currentTimeMillis();
for(int i = 0; i < 100000; i++){
StringBuffer c = new StringBuffer("c");
c.append(String.valueOf(i));
//c.append(String.valueOf(i)).append(String.valueOf(i)).append(String.valueOf(i)).append(String.valueOf(i)).append(String.valueOf(i)).append(String.valueOf(i)).append(String.valueOf(i)).append(String.valueOf(i));
}
end = System.currentTimeMillis();
System.out.println("double StringBuffer:" + (end - start));
}
public static void concat(){
String initial = "";
long start = System.currentTimeMillis();
for(int i = 0; i < 100000; i++){
initial = initial.concat("d");
}
long end = System.currentTimeMillis();
System.out.println("concat:" + (end - start));
start = System.currentTimeMillis();
for(int i = 0; i < 100000; i++){
String d = "d";
d = d.concat(String.valueOf(i));
d = //d.concat(String.valueOf(i)).concat(String.valueOf(i)).concat(String.valueOf(i)).concat(String.valueOf(i)).concat(String.valueOf(i)).concat(String.valueOf(i)).concat(String.valueOf(i)).concat(String.valueOf(i));
}
end = System.currentTimeMillis();
System.out.println("double concat:" + (end - start));
}
}
结果如下:
结果:从结果可以看出,在拼接10万次的情况下,循环拼接同一个字符串的时候,StringBuilder和StringBuffer的优势是巨大的,仅仅花了不到10ms的时间,而StringBuilder稍快一点。而直接用“+”号和concat都花费了秒级的时间。但是在对10万字符串都拼接一个串的时候,concat的情况发生了逆转,速度甚至比StringBuilder和StringBuffer更快。
原理分析
1.加号拼接
打开编译后的字节码我们可以发现加号拼接字符串jvm底层其实是调用StringBuilder来实现的,也就是说”a” + “b”等效于下面的代码片。
String a = "a";
StringBuilder sb = new StringBuilder();
sb.append(a).append("b");
String str = sb.toString();
但并不是说直接用“+”号拼接就可以达到StringBuilder的效率了,因为用“+”号每拼接一次都会新建一个StringBuilder对象,并且最后toString()方法还会生成一个String对象。在循环拼接十万次的时候,就会生成十万个StringBuilder对象,十万个String对象,这简直就是噩梦。
2.concat拼接
concat的源代码如下,可以看到,concat其实就是申请一个char类型的buf数组,将需要拼接的字符串都放在这个数组里,最后再转换成String对象。
public String concat(String str) {
int otherLen = str.length();
if (otherLen == 0) {
return this;
}
int len = value.length;
char buf[] = Arrays.copyOf(value, len + otherLen);
str.getChars(buf, len);
return new String(buf, true);
}
3.StringBuilder/StringBuffer
这两个类实现append的方法都是调用父类AbstractStringBuilder的append方法,只不过StringBuffer是的append方法加了sychronized关键字,因此是线程安全的。append代码如下,他主要也是利用char数组保存字符,通过ensureCapacityInternal方法来保证数组容量可用还有扩容。
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;
}
他扩容的方法的代码如下,可见,当容量不够的时候,数组容量右移1位(也就是翻倍)再加2,以前的jdk貌似是直接写成int newCapacity = (value.length * 2) + 2,后来优化成了右移,可见,右移的效率还是比直接乘更高的。
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;
}
原因分析
1.循环拼接字符串
通过实验我们发现,在循环拼接同一个字符串的时候,他们效率的按快慢排序是
StringBulider > StringBuffer >> String.concat > “+”。
StringBulider比StringBuffer更快这个容易理解,因为StringBuffer的方法是sychronized修饰的,同步的时候会损耗掉一些性能。StringBulider和String.concat的区别,主要在扩容上,String.concat是需要多少扩多少,而StringBulider是每次翻两倍,指数级扩容。在10万次拼接中,String.concat需要扩容10万次,而StringBuilder只需要扩容log100000次(大约17次),除此之外,concat每次都会生成一个新的String对象,而StringBuilder则不必,那StringBuilder如此快就不难解释了。至于直接用“+”号连接,之前已经说了,它会产生大量StringBuilder和String对象,当然就最慢了。
2.大量字符串拼接
在只拼接少量字符串的情况下的时候,他们效率的按快慢排序是
String.concat > StringBulider > StringBuffer > “+”。
为什么在拼接少量字符串的时候,String.concat就比StringBuilder快了呢,原因大致有两点,一是StringBuilder的调用栈更深,二是StringBuilder产生的垃圾对象更多,并且它重写的toString方法为了做到不可变性采用了“保护性拷贝”,因此效率不如concat。
详细原因分析参考:concat和StringBuilder性能分析
保护性拷贝见:保护性拷贝
当拼接的字符串少的时候,concat因为上述优势略快,但是当一次性拼接字符串多的时候,StringBuilder的扩容更少优势便会开始展现出来,例如一次拼接8个字符串,concat的效率就已经明显不如StringBuilder了,如下图。
结论
从以上分析我们可以得出以下几点结论
1.无论如何直接用“+”号连接字符串都是最慢的
2.在拼接少数字符串(不超过4个)的时候,concat效率是最高的
3.多个字符串拼接的时候,StringBuilder/StringBuffer的效率是碾压的
4.在不需要考虑线程安全问题的时候,使用StringBuilder的效率比StringBuffer更高