Java——String物件
前言
實際上任何語言都沒有提供字串這個概念,而是使用字元陣列來描述字串。Java裡面嚴格來說也是沒有字串的,在所有的開發裡面字串的應用有很多,於是Java為了應對便建立了String類這個字串類。使用""定義的內容都是字串,理解Java的String類需要從類的角度和記憶體關係上分析這個類。
下面將介紹:
- String類物件的兩種例項化方式
- 使用"=="和equals比較字串是否相等
- String常量為匿名物件
- String兩種例項化方式的區別
- 字串一旦定義則不可變
- 位元組與字串
- 字串中的方法分類
- 過載"+"與StringBuilder
- StringBuffer與StringBuilder
String類物件的兩種例項化方式
String name1 = "Sakura"; //直接賦值方式
String name2 = new String("Sakura"); //利用構造方法例項化
使用"=="和equals比較字串是否相等
使用"=="比較的是兩個物件在記憶體中的地址是否一致,也就是比較兩個物件是否為同一個物件。
使用equals()方法比較的是物件的值是否相等,name1和name2所指物件的值都是"Sakura"所以輸出為true。
String常量為匿名物件
像"Sakura"這樣的字串不屬於基本資料型別,而是作為String類的
驗證"Sakura"字串是否為匿名物件:
"Sakura"可以呼叫String類的方法,由此可見"Sakura"是一個物件。
建立String物件的直接賦值方式相當於將一個匿名物件設定了一個名字。在前篇文章裡我們說直接使用"new 類名稱();"的方法建立的是一個匿名物件,String類的匿名物件卻不是這樣的,String類的匿名物件是由系統自動生成不是由使用者直接建立。
下面會講JVM中關於字串的記憶體分配管理。
Notice |
圖中的程式碼實際隱含了一個避免出現NullPointerException的小技巧。
若是我們像下面這樣寫字串比較程式碼:
未知name1是否指向了一個物件,所以會存在丟擲空指標異常的情況。為了避免空指標異常我們就可以將字串常量寫在前面。
兩種例項化方式的區別
直接賦值方式
前面講過直接賦值方式就是將一個字串的匿名物件設定了一個名字。
"=="比較的是兩個物件記憶體地址是否一致,由輸出結果可以看出name1和name2指向了同一塊堆空間。
為什麼不是在堆空間中開闢兩個"Sakura"物件而是讓name1和name2指向同一個物件呢?
這個需要談到JVM的共享設計模式。
JVM的底層實現實際上在堆中存在一個物件池(常量池,不一定只儲存String物件),當我們使用直接賦值方式定義String類物件,那麼JVM會將此字串物件使用的匿名物件就是如"Sakura"字串入池儲存。如果後面還有其他String物件採用同樣方式且設定同樣內容時,將不會開闢新的堆空間,而是繼續使用相同的空間。
採用構造方法例項化
String name = new String("Sakura");
分析以上語句開闢空間情況:
開闢了一塊棧記憶體儲存了物件引用; 開闢了兩塊堆空間,一塊在常量池中儲存"Sakura"字串常量另一塊在堆中儲存這個物件。
當堆中的物件若是沒有引用指向就是垃圾物件會被GC清理掉。所以,這種構造方式會造成一塊堆空間的浪費。
若是希望,此方式的物件也可以入池儲存也是有方法的,就是利用String類的intern方法。
public class Test {
public static void main(String[] args) {
String name1 = new String("Sakura").intern(); //返回一個匿名物件 name1就指向的是常量池中的"Sakura"
String name2 = "Sakura";
System.out.println(name1==name2);
}
}
/*
output:
true
*/
但是方法真的顯得很麻煩!
總結一下兩種例項化方法的區別:
- 直接賦值方式:只會開闢一塊堆記憶體空間,並且自動儲存在常量池中,以供我們下次重複使用。
- 構造方法:會開闢兩塊堆記憶體空間,其中在常量池的會成為垃圾空間。
字串一旦定義便不可變
String name = "Amy";
String name = "Smith";
String name = "Amy" + "Smith";
Java定義了String內容不能被改變。分析堆記憶體,是可以知道字串物件內容的改變是利用了引用關係的變化而實現的。每一次的改變都會產生垃圾空間,所以不要頻繁更改定義好的字串。
位元組與字串
檢視API可以看見有許多關於位元組的方法。位元組使用byte描述,是Java中的基本資料型別之一,使用位元組一般主要用於資料的傳輸或者進行編碼轉換的時候使用。在String中有許多將字串轉換為位元組陣列的操作,目的就是為了傳輸轉換。
字串中的方法分類
在程式開發中對字串的操作是常事的,那麼在Java中對字串操作方法也是有很多的。主要分為下面幾類,關於每種方法的使用可以檢視API,但是最好還是幾乎要掌握完。
- 比較方法
- 查詢方法
- 替換方法
- 擷取方法
- 拆分方法
過載"+"與StringBuilder
Java中不允許程式設計師過載任何操作符,但是Java內部過載了兩個用於String類的操作符"+"和"+="。操作符"+"可以用於連線字串,操作符"+="用於將連線後的字串再次賦給原字串引用。
由前面所知,不斷使用"+="連線,會產生很多的中間垃圾物件,而且連線的越多也就越浪費空間和時間。垃圾物件佔用空間,Java垃圾回收器清理越耗時。
雖然使用這種方式連線字串,從分析堆疊圖來看很費空間和耗時。但是,JVM在執行程式會不會給優化呢。我們反編譯下面程式來觀察一下:
javap -c StringContact
public class StringContact {
public static void main(String[] args) {
String str1="hello";
String str2="Sakura"+str1+"!";
System.out.println(str2);
}
}
/*
output:
Sakurahello!
*/
可以看出:編譯器自動引入了java.lang.StringBuilder類。編譯器先自動建立了一個StringBuilder物件,每次字串連線時呼叫StringBuilder的append()方法,呼叫了兩次。最後,呼叫toString()生成最終的字串,存在str2中使用命令astroe_2。
編譯器自主使用StringBuilder類,因為它更高效。StringBuilder物件含有一個緩衝區來處理字串,所以可以修改刪除字串。在上面程式碼中建立了一個StringBuilder物件,連線字串時只是不斷呼叫其append方法,沒有建立反覆建立物件。
使用下面的例子深入看看編譯器的優化程度:
public class CompareStringBuilder {
public String implicit(String[] fields) { //使用String隱式的字串連線
String result="";
for(int i=0; i<fields.length; i++)
result += fields[i];
return result;
}
public String explicit(String[] fields) { //使用StringBuilder的append方法連線字串
StringBuilder result=new StringBuilder();
for(int i=0; i<fields.length; i++)
result.append(fields[i]);
return result.toString();
}
}
反編譯上述程式:
若是不滿足Code 8的大於等於迴圈次數的話,那麼Code 5到Code 35就是一個迴圈,並且在每一次迴圈中StringBuilder物件都會被建立。
可以看出這個程式碼只是在最初建立了一次StringBuilder物件,並且在迴圈中是一直使用append()方法修改字串。
以上兩段程式碼可以看出編譯器對我們程式碼的優化程度,字串較簡單時可以直接使用String,若是需要大量連線則需要可考慮StringBuilder類。
StringBuilder與StringBuffer
與String物件比較StringBuffer是一個可變的物件,可以通過其自帶的某些方法改變其值的長度和內容。如使用append()方法追加字串。
同StringBuilder一樣,StringBuffer對字串的修改效率要高於String。
檢視JDK文件可知,StringBuilder是在JAVA 5中提出,與StringBuffer擁有的方法幾乎相似,可以看成StringBuffer一種“替換”形式。二者的主要區別是, StringBuffer是執行緒安全的,而StringBuilder是執行緒不安全的。
檢視JDK原始碼,可以發現StringBuffer在其每個方法前都加了synchronized關鍵字(用於多執行緒執行緒同步)
如append方法
@Override
public synchronized StringBuffer append(Object obj) {
toStringCache = null;
super.append(String.valueOf(obj));
return this;
}
因為StringBuilder是執行緒不安全的,所以一般用在單執行緒,因為其不需要管理執行緒同步這些問題所以速度會比StringBuffer快。
小結
本文主要介紹了:
String物件使用equals的比較物件是否相等以及使用"=="判斷物件是否同一物件;String物件的兩種例項化方式,直接賦值不會產生垃圾空間,並且會存入常量池中,構造方法會產生中間垃圾物件且不會入池;String物件是一個不可變物件,String類物件內容改變是依靠引用關係變化,實際物件並沒有發生任何變化;若是經常改變字串值需要使用StringBuilder或者StringBuffer。
部分內容參考自《Java程式設計思想》(第四版)