前言
在平時開發中,我們很多時候都會用到String
,StringBuffer
,StringBuilde
這三者。那麼這三者究竟是什麼呢,下面一一講述。
String
檢視api
文件,可以知道,String
是繼承object
類,並實現了序列化介面,字元序列介面,字串排序介面。String
是Java中的字串,Java程式中的所有字串字面值(如"abc")都作為此類的例項實現,字串是常量,它們的值在建立之後不能更改,字串緩衝區支援可變的字串。因為String
物件是不可變的,所有可以共享,例如:String str1 = "abc";
等效於char data[] = {'a','b','c'};String str2 = new String(data);
,因為String
是不可變的物件,每次對String
型別改變的時候其實都等同於生成一個新的String
物件,然後將指標指向新的String
物件,所以經常改變內容的字串最好不要用String
,因為每次生成物件都會對系統效能產生影響,當記憶體多了無引用的物件,JVM
的GC就會工作,頻繁GC就會造成介面的卡頓,下面直接上例子:
直接賦值和建立物件
情況一
String str1 = "abc";
char data[] = {'a','b','c'};
String str2 = new String(data);
//輸出true
System.out.println(str1.equals(str2));
//輸出false
System.out.println(str1 == str2);
String str3 = "This is Java";
String str4 = new String("This is Java");
//輸出true
System.out.println(str3.equals(str4));
//輸出false
System.out.println(str3 == str4);
複製程式碼
上面程式碼str1
和str3
是直接賦值的,str2
是通過字元陣列來構建一個字串常量,str4
是直接通過new
關鍵字來建立物件。可以看到用equals
比較str1
和str2
,str3
和str4
是相等的,用==
比較str1
和str2
,str3
和str4
是不相等的。這裡簡單說一下equals
和==
的簡單區別:
- ==操作符的作用
- 比較基本資料型別是否相同
- 判斷引用是否指向堆記憶體的同一塊地址,也就是是否指向同一個物件
- equasl方法的作用
- 判斷兩個變數是否對同一個物件的引用,也就是記憶體堆中的內容是否相同,其實就是對
String
物件所封裝的字串內容作比較,具體的值是否相等,如果兩個String
物件所封裝的字元型內容相同,則equals()
方法將返回true
。
而上面的情況因為str1
和str2
是兩個不同的物件,但是內容相等,所以==
返回了false,而equals
返回true,str3
和str4
也是一樣道理,從上面可以簡單得出一個結論,無論上直接賦值還是通過new
關鍵字來建立String
物件,都會生成兩個不同的String
物件,即使字元型內容相同。
情況二
String str5 = "This is Java";
String str6 = "This is";
String str7 = "This is" + " Java";
//輸出true
System.out.println(str5.equals(str7));
//輸出true
System.out.println(str5 == str7);
複製程式碼
從上面程式碼可以看出,無論是用==
或者equals
來比較兩個沒有引用並且內容值是相等返回都是true,也就是String str7 = "This is" + " Java";
這句話沒有建立新的物件,而是引用指向str5
的地址。
情況三
String str8 = "This is Java";
String str9 = "This is";
String str10 = str9 + " Java";
//輸出true
System.out.println(str8.equals(str10));
//輸出false
System.out.println(str8 == str10);
複製程式碼
上面發現用==
比較時,str8
和str10
不是指向同一個地址,這是因為在編譯時str8
和str9
就已經確定了,而str10
是引用變數,不會再編譯時確定,所以會建立String
物件。
情況四
String str11 = "This is Java";
String str12 = new String("This is Java");
String str13 = str12;
//輸出true
System.out.println(str11.equals(str12));
//輸出true
System.out.println(str11.equals(str13));
//輸出true
System.out.println(str12.equals(str13));
//輸出false
System.out.println(str11 == str13);
//輸出false
System.out.println(str11 == str12);
//輸出true
System.out.println(str12 == str13);
複製程式碼
因為三個String
物件的值都是相等的,所以用equals
來比較是返回true,因為str13
是指向str12
,相當於傳遞引用,所以用==
來比較是相等的,都是同一個物件,用一張圖來比較直觀:
String str11 = "This is Java";
在常量池已經建立This is Java
這個常量,所以String str12 = new String("This is Java");
這句程式碼不會再常量池建立物件,只會在堆上建立物件This is Java
,也就是說new String
建立字串它其實分兩步操作:
- 在堆上建立物件
- 檢查常量池有沒有字串字面量,如果沒有就在常量池建立常量,如果存在就不做任何操作
情況五
String str14 = "This is Java";
String str15 = "This is Java";
String str16 = "This is Java";
//輸出true
System.out.println(str14.equals(str15));
//輸出true
System.out.println(str14.equals(str16));
//輸出true
System.out.println(str15.equals(str16));
//輸出true
System.out.println(str14 == str15);
//輸出true
System.out.println(str14 == str16);
//輸出true
System.out.println(str15 == str16);
複製程式碼
直接上圖:
情況六
String str17 = new String("This is Java");
String str9 = str17.intern();
String str18 = "This is Java";
//輸出false
System.out.println(str9 == str17);
//輸出true
System.out.println(str9 == str18);
複製程式碼
上面呼叫了intern
這個方法,這句話就是把字串物件加入常量池中,實際操作如下,直接上圖吧:
總結
String a = "xxx"
可能建立一個或者不建立物件,當xxx
這個字元常量在String
常量池不存在,會在String
常量池建立一個String
物件,然後a
會指向這個記憶體地址,後面繼續用這種方式繼續建立xxx
字串常量,始終只有一個記憶體地址被分配。String a = new String("xxx")
至少建立一個物件,也有可能兩個。只要用到new
就肯定在堆上建立一個String
物件,並且檢查String
常量池是否存在,如果不存在就會在Stirng
常量池建立這個String
物件,存在就不建立。
StringBuffer字串變數
在api
文件上,同樣可以知道StringBuffer
繼承Object
類,並實現了序列化介面,能夠被新增char
序列和值的物件介面,可讀序列介面。和String
相比,沒有實現Comparable
,而是實現了Appendable
。它是執行緒安全的可變字元序列,一個類似於String
的字串緩衝區,但是不能修改,雖然在任意時間點上它都包含某種特定的字元序列,但通過某些方法呼叫的可以改變該序列的長度和內容。StringBuffer
上的主要操作是append
和insert
方法,可以過載這些方法,以接受任意型別的資料,每個方法都能有效地將給定的資料轉換成字串,然後將該字串的字元追加或者插入到字串緩衝區中。append
方法始終將這些字元新增到緩衝區的末端,而insert
方法則在指定的點新增字元。StringBuffer
和String
不一樣,它是**字串變數,是可以改變的物件,當對字串做操作時,就是在物件上做操作,不會像String
一樣建立額外的物件進行操作。
StringBuffer str20 = new StringBuffer("This is");
//StringBuffer後面追加內容
str20.append(" Java");
System.out.println(str20); //輸出:This is Java
//刪除第6-8個字元
str20.delete(5, 8);
System.out.println(str20); //輸出:This Java
//在9個位置插入
str20.insert(9," is good");
System.out.println(str20); //輸出:This Java is good
//替換5-9
str20.replace(5, 9, "C++");
System.out.println(str20); //輸出:This C++ is good
//倒序
str20.reverse();
System.out.println(str20); //輸出:doog si ++C sihT
//變成大寫
System.out.println(str20.toString().toUpperCase()); //輸出:DOOG SI ++C SIHT
//第一種方案StringBuffer轉化成String
String str21 = new String(str20);
System.out.println(str21); //輸出:doog si ++C sihT
//第二種方案
String str22 = str20.toString();
System.out.println(str22); //輸出:doog si ++C sihT
複製程式碼
StringBuilder字串變數
和StringBuffer
一樣,也是實現Serializable
,Appendable
,CharSequence
介面。一個可變的字元序列,這個類提供一個與StringBuffer
相容的API,但是不保證同步,該類被設計用作StringBuffer
的一個簡易替換,用在字串緩衝區被單個執行緒使用的時候。如果可能,建議優先採用該類,因為在大多數實現中,比StringBuffer
要快。在StringBuilder
上的主要操作是append
和insert
方法,可過載這些方法,以接受任意型別的資料。每個方法都能有效地將給定的資料轉換成字串,然後將該字串的字元追加或插入到字串生成器中。append
方法始終將這些字元新增到生成器的末端;而 insert
方法則在指定的點新增字元。將StringBuilder
的例項用於多個執行緒是不安全的。如果需要這樣的同步,則建議使用StringBuffer。
主要一些操作:
StringBuilder str23 = new StringBuilder("This is");
//StringBuilder後面追加內容
str23.append(" Java");
System.out.println(str23); //輸出:This is Java
//刪除第6-8個字元
str23.delete(5, 8);
System.out.println(str23); //輸出:This Java
//在9個位置插入
str23.insert(9," is good");
System.out.println(str23); //輸出:This Java is good
//替換5-9
str23.replace(5, 9, "C++");
System.out.println(str23); //輸出:This C++ is good
//倒序
str23.reverse();
System.out.println(str23); //輸出:doog si ++C sihT
//變成大寫
System.out.println(str23.toString().toUpperCase()); //輸出:DOOG SI ++C SIHT
//第一種方案StringBuffer轉化成String
String str24 = new String(str23);
System.out.println(str24); //輸出:doog si ++C sihT
//第二種方案
String str25 = str23.toString();
System.out.println(str25); //輸出:doog si ++C sihT
複製程式碼
三者速度比較
//做賦值操作
//String賦值
String str = new String("I Love Android");
long starttime1 = System.currentTimeMillis();
for (int i = 0; i < 100000; i++) {
str = str + "!";
}
long endtime1 = System.currentTimeMillis();
System.out.println("String花費的時間 :" + (endtime1 - starttime1));
//StringBuilder
StringBuilder str3 = new StringBuilder("I Love C++");
long starttime3 = System.currentTimeMillis();
for (int i = 0; i < 100000; i++) {
str3 = str3.append("!");
}
long endtime3 = System.currentTimeMillis();
System.out.println("StringBuilder花費的時間 :" + (endtime3 - starttime3));
//StringBuffer
StringBuffer str2 = new StringBuffer("I Love Java");
long starttime2 = System.currentTimeMillis();
for (int i = 0; i < 100000; i++) {
str2 = str2.append("!");
}
long endtime2 = System.currentTimeMillis();
System.out.println("StringBuffer花費的時間 :" + (endtime2 - starttime2));
複製程式碼
執行截圖:
總結
- String :不可變類,任何對String的改變都會引發新的String物件的生成,適用於少量的字串操作的情況
- StringBuffer :執行緒安全,任何對它所指代的字串的改變都不會產生新的物件,適用多執行緒下在字元緩衝區進行大量操作的情況
- StringBuilder :執行緒不安全,因此不適合多執行緒中使用,適用於單執行緒下在字元緩衝區進行大量操作的情況
- 速度執行方面:StringBuilder > StringBuffer > String