常見的四種字符串拼接方法分別是
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更高