今天咱們一起來說說java家族中的使用頻率最廣泛的String
-
String的特性
在Sring源碼的類注釋當中有如下一段話:
這說明了String的一個重要特性,String 是value不可改變的
然而String類還使用了final來修飾,表明String的第二個重要特性,** String是不可被繼承的** String的定義方法
一般我們在代碼中可以使用如下三種方法定義String對象
String str1 = new String("cat");
String str2 = "cat";
String str3 = "c"+"at";
String str4 = str1+"cat";
第一種方式直接通過關鍵字new創建對象,毫無疑問str1指向堆內存
第二種直接賦值,Str2指向常量池
第三種通過+號連接賦,最終str3也是指向常量池
第四種是帶三種的延伸,這個最終str4是指向堆內存
對于前三種方式大家應該都沒什么疑問,那么第四種方法為什么也會指向堆內存呢?欲知真相還需從字節碼入手啊!
先來看看幾個經典的面試題
- First
String s1 = "abc";
String s2 = "abc";
System.out.println("s1 == s2 : " + (s1 == s2)); //true
So esay,對吧!
- Second
String s1 = "abc";
String s2 = new String("abc");
String s3 = new String("abc");
System.out.println("s2 == s3 : " + (s2 == s3)); //false
System.out.println("s1 == s3 : " + (s1 == s3));//false
還是很簡單,對么,只要有點基礎的人都知道答案呢!
- Third
public class Test{
public static void main(String[] args){
String str1 = "ab" + "cd"; //
String str11 = "abcd";
//System.out.println("str1 == str11 : " + (str1 == str11)); //true
String str2 = "ab"+2;
String str22 = "ab2";
//System.out.println("str2 == str22 : " + (str2 == str22)); //true
final String str3 = "ab";
String str31 = str3+"cd";
String str32 = "abcd";
//System.out.println("str2 = str3 : " + (str2 == str3)); // false
}
}
這個對新手來說可能就有點不知所措了,來來來,上我們的終極利器,請看下面編譯之后的字節碼
因為常量的值在編譯期間就已經確定了,這里"ab","cd","2"都是常量,所以編譯器會自動對
String str1 = "ab" + "cd";String str2 = "ab"+2;
進行優化,效果相當于String str1 = "abcd";String str2 = "ab2";
這下你明白為什么結果會是true了吧
- Fourth
public class Test{
public static void main(String[] args){
String str2 = "ab";
String str3 = "cd";
String str41 = str2 + str3;
String str42 = "ab"+str3;
String str43 = str2+"cd";
String str5 = "abcd";
//System.out.println("str41 = str5 : " + (str41 == str5)); // false
//System.out.println("str42 = str5 : " + (str42 == str5)); // false
//System.out.println("str43 = str5 : " + (str43 == str5)); // false
}
}
這個跟例三差不多,只不過例三是常量,這里變成了變量,結果就迥然不同,下面我們來看看這段代碼的字節碼長啥樣子
發現虛擬機在處理變量字符串用+號連接的時候是生成了一個StringBuilder對象,然后調用StringBuilder的append方法,最后在調用StringBuilder的toString方法返回。StringBuilder的toString方法源碼如下:
這下就徹底明白了吧。它是從新new一個String對象,結果當然為false了!
針對==
的總結如下
眾所周知,==
是判斷內存地址的,那么我們判斷的話看引用最終指向哪里不就可以了么
- new出來的對象是存放在堆內存中的,引用指向堆內存,那么地址肯定是唯一的了,所以結果肯定為false
- 直接聲明的字符串引用是指向常量池的,而常量池中的相同內容的字符串只有一份,所以結果為true
- 通過+號連接的字符串分如下兩種情況
3.1 如果+號兩邊連接的是常量,那么會在編譯期間進行優化,結果同2
3.2 如果+號兩邊連接的有變量,不管是new出來的也好,直接聲明的也好,java虛擬機執行的時候都會生成一個StringBuilder對象sb,然后調用sb.apend()
方法,最后通過sb.toString()
返回一個新的字符串。那么此時引用就指向堆內存,結果同1
String.intern()
當一個String實例str調用intern()方法時,虛擬機會查找常量池中是否有相同Unicode的字符串常量,如果有,則返回其的引用;如果沒有,則在常量池中增加一個Unicode等于str的字符串并返回它的引用;PS:我并沒有想到什么時候會沒有
public class Test{
public static void main(String[] args){
String s00 = "kvill";
String s11 = new String("kvill");
String s22 = new String("kvill");
System.out.println( s00 == s11 ); //false
s11.intern(); //雖然執行了s1.intern(),但它的返回值沒有賦給s1
s22 = s22.intern(); //把常量池中"kvill"的引用賦給s2
System.out.println( s00 == s11); //flase
System.out.println( s00 == s11.intern() ); //true 說明s11.intern()返回的是常量池中"kvill"的引用
System.out.println( s00 == s22 ); //true
}
}
關于String,StringBuffer,StringBuilder的區別
- String是值不可變的,每次進行連接操作是都是返回一個新的String對象,StringBuffer,StringBuilder是值可變的,操作是返回的是this
這也就是為什么在進行大量字符串連接運算時,不推薦使用String,而推薦StringBuffer和StringBuilder。 - StringBuffer是線程同步的,安全性高,但執行效率低
StringBuilder是非線程同步的,安全性低,但執行效率高
這是StringBuffer,StringBuilder append方法的源碼
THE END
如有錯誤之處還請大家不吝賜教,多多指正,共同進步!