String 是不可變的,一旦定義了,就不能再去修改字符串的內容。
先看下面兩行代碼:
String a = "Hello"; a = a + " world"
通常情況下很容易誤解為修改了字符串對象的內容。其實不然,真實的操作則是
並不是 "Hello" 改變了,而是指向 "Hello" 的引用 a重新指向了新對象。
StringBuilder 在很大程度上類似 ArrayList:
StringBulder ArrayList維護了一個 char 數組
(其實這個數組屬於它的父類 AbstractStringBuilder)
維護了一個 Object 數組 append 方法向後面增加新元素 add(E e) 方法向後面增加新元素 insert 方法向中間某位置插入新元素 add(int index, E e) 向某位置增加新元素 deleteCharAt(int index) 刪除某位置的元素 remove(int index) 刪除某位置的元素 添加元素時候空間不夠會動態擴容 就羅列這麼多吧~很明顯如果需要連續拼接很多字符串的話 StringBuilder 比 String 更加方便。而且在性能方面也有考究這點我們稍後再說。
StringBuffer 基本上和 StringBuilder 完全一樣了。明顯的不同就是 StringBuffer 是線程安全的,除了構造方法之外的所有方法都用了 synchronized 修飾。
相對來說安全一些,但是性能上要比 StringBuilder 差一些了。
先看一段代碼:
public class Test {
public static void main(String[] args) {
String aa = "33";
aa = aa + 3 + 'x' + true + "2";
aa = aa + 8;
String bb = aa + "tt";
System.out.println(bb);
}
}
使用編譯工具編譯
javac Test.java
同級目錄會生成 Test.class,我們再對 Test.class 進行反匯編
javap Test.class
得到下面的代碼:
public class Test {
public Test();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
public static void main(java.lang.String[]);
Code:
0: ldc #2 // String 33
2: astore_1
3: new #3 // class java/lang/StringBuilder
6: dup
7: invokespecial #4 // Method java/lang/StringBuilder."<init>":()V
10: aload_1
11: invokevirtual #5 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
14: iconst_3
15: invokevirtual #6 // Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
18: bipush 120
20: invokevirtual #7 // Method java/lang/StringBuilder.append:(C)Ljava/lang/StringBuilder;
23: iconst_1
24: invokevirtual #8 // Method java/lang/StringBuilder.append:(Z)Ljava/lang/StringBuilder;
27: ldc #9 // String 2
29: invokevirtual #5 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
32: invokevirtual #10 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
35: astore_1
36: new #3 // class java/lang/StringBuilder
39: dup
40: invokespecial #4 // Method java/lang/StringBuilder."<init>":()V
43: aload_1
44: invokevirtual #5 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
47: bipush 8
49: invokevirtual #6 // Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
52: invokevirtual #10 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
55: astore_1
56: new #3 // class java/lang/StringBuilder
59: dup
60: invokespecial #4 // Method java/lang/StringBuilder."<init>":()V
63: aload_1
64: invokevirtual #5 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
67: ldc #11 // String tt
69: invokevirtual #5 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
72: invokevirtual #10 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
75: astore_2
76: getstatic #12 // Field java/lang/System.out:Ljava/io/PrintStream;
79: aload_2
80: invokevirtual #13 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
83: return
}
我們把每出現一次 “=” 算成一次拼接,那麼每次拼接都會創建一個 StringBuilder 對象。
當遇到大規模的場景中,比如循環次數很多,就像下面的例子:
public class Test1 {
public static void main(String[] args) {
Test1 test = new Test1();
System.out.println(test.testString());
System.out.println(test.testStringBuilder());
}
public long testString(){
String a = "";
long start = Calendar.getInstance().getTimeInMillis();
for(int i=0;i<100000;i++){
a += i;
}
long end = Calendar.getInstance().getTimeInMillis();
return end-start;
}
public long testStringBuilder(){
StringBuilder a = new StringBuilder();
long start = Calendar.getInstance().getTimeInMillis();
for(int i=0;i<100000;i++){
a.append(i);
}
long end = Calendar.getInstance().getTimeInMillis();
return end-start;
}
}
輸出:
22243 16
耗時比較,前者呈指數級增長,而後者是線性增長。性能上相差甚遠。
甚至如果我們已經知道了容量,還可以繼續優化,一次性分配一個 StringBuilder,避免擴容時候的開銷。參考下面例子。
public class Test2 {
public static void main(String[] args) {
Test2 test = new Test2();
for(int i=0;i<5;i++){
System.out.println(test.testStringBuilder() + "---" + test.testStringBuilder2());
}
}
public long testStringBuilder(){
StringBuilder a = new StringBuilder();
long start = Calendar.getInstance().getTimeInMillis();
for(int i=0;i<10000000;i++){
a.append(1);
}
long end = Calendar.getInstance().getTimeInMillis();
return end-start;
}
public long testStringBuilder2(){
StringBuilder a = new StringBuilder(10000000);
long start = Calendar.getInstance().getTimeInMillis();
for(int i=0;i<10000000;i++){
a.append(1);
}
long end = Calendar.getInstance().getTimeInMillis();
return end-start;
}
}
輸出:
78---16 62---31 47---15 63---31 47---31
提前分配,耗時更短~
原則很簡單:不要使用字符串連接操作符來合並多個字符串,除非性能無關緊要。應該使用 StringBuilder 的 append 方法。