文章目錄
1 不可變的String
在 Java中,字符串屬於對象,Java提供了 String類 來創建和操作字符串
通過查看 String源碼,可以發現
- String類是
final類
,並且它的成員方法都默認爲final方法
- String類是通過
char數組
來保存字符串的 - String類的 sub,concat,replace等操作,都不是在原有字符串上進行的,而是重新生成了一個新的字符串,即進行這些操作後,最原始的字符串並沒有被改變
char[] helloArray = {'h','e','l','l','0'};
String helloString = new String(helloString);
String對象是不可變的
。
String類中每一個看起來會修改String值的方法,實際上都是創建了一個全新的String對象,以包含修改後的字符串的內容。
String str1 = "java";
String str2 = "java";
System.out.println(str1 == str2); // true
在代碼中,可以創建同一個String對象的多個別名,而它們所指向的對象是相同的
,一直呆在一個單一的物理位置上,從未動過。
內存分析
String str = "hello"; str = str+"world";
;
初始String值爲“hello”,然後在這個字符串後面加上新的字符串“world”,這個過程是需要重新在棧堆內存中開闢內存空間的,最終得到了“hello world”字符串也相應的需要開闢內存空間,這樣短短的兩個字符串,卻需要開闢三次內存空間,不得不說這是對內存空間的極大浪費。
2 StringBuffer和StringBuilder
當對字符串進行修改的時候,需要使用 StringBuffer與 StringBuilder類。
和 String類不同的是,StringBuffer和StringBuilder類的對象能夠被多次修改,並且不產生新的未使用的對象
通過查看源碼發現,StringBuilder和StringBuffer類擁有的成員屬性以及成員方法基本相同,區別是 StringBuffer類的成員方法前面多了一個關鍵字:synchronized。即 StringBuffer是線程安全的,但是 StringBuilder的訪問速度更快,因此,一般用 StringBuilder.
注意:
String 可以賦空值,StringBuilder,StringBuffer不行,它們是對象,必須先 new,獲得具體對象再使用
StringBuffer s = null; //結果警告:Null pointer access: The variable result can only be null at this location
StringBuffer s = new StringBuffer();//StringBuffer對象是一個空的對象
StringBuffer s = new StringBuffer(“abc”);//創建帶有內容的StringBuffer對象,對象的內容就是字符串”
3 String,StringBuffer,StringBuilder三者的執行效率
一般情況下,StringBuilder > StringBuffer > String
不是所有情況下都這樣
比如:String str = “hello”+“world”; 的效率,就比 StringBuilder sBuilder = new StringBuilder().append(“hello”).append(“world”); 要高
因此,這三個類各有利弊,應當根據不同的情況來進行選擇使用
- 當字符串相加操作或者改動較少的情況下,建議使用 String str = “hello”;這種形式
- 當字符串相加操作較多的情況下,建議使用 StringBuilder,如果採用了多線程,則使用 StringBuffer
4 String的重載 “+”
在 Java中,
唯一被重載的運算符就是用於 String的 "+" 與"+="
除此之外,Java不允許程序員重載其他的運算符
public class StringTest {
String a = "abc";
String b = "mongo";
String info = a + b + 47;
}
String對象是不可變的,所以在上述的代碼過程中可能會是這樣工作的:
(1)"abc" + "mongo"
創建新的String對象abcmongo
;
(2)"abcmongo" + "47"
創建新的String對象abcmongo47
;
(3)引用info 指向最終生成的String。
但是這種方式會生成一大堆需要垃圾回收的中間對象,性能相當糟糕。
4.1 編譯器的優化處理
通過反編譯代碼會發現,編譯器自動引入了StringBuilder類
。
編譯器創建了一個StringBuilder對象,並調用StringBuilder.append()方法,最後調用toString()生成結果,從而避免中間對象的性能損耗。
編譯器優化String對象的連接,而下面這種情況會直接連接作爲常量。
public class StringTest {
String info = "Andy" + "24" + "Developer";
}
4.2 編譯器的優化是有限度的
性能較低的代碼
public void implicitUseStringBuilder(String[] values) {
String result = "";
for (int i = 0 ; i < values.length; i ++) {
result += values[i];
}
System.out.println(result);
}
通過反編譯發現,StringBuilder對象創建發生在循環之間
,也就是意味着有多少次循環就會創建多少個StringBuilder對象,這樣明顯性能較低。
性能較高的代碼
public void explicitUseStringBuider(String[] values) {
StringBuilder result = new StringBuilder();
for (int i = 0; i < values.length; i ++) {
result.append(values[i]);
}
}
StringBuilder的創建,位於循環之外,所以不會多次創建 StringBuilder
綜上,循環體中需要儘量避免隱式或者顯式創建StringBuilder。