1.String 與char的區別
1 char是表示的是字符,定義的時候用單引號,只能存儲一個字符。例如; char='d'.
而String表示的是字符串,定義的時候用雙引號,可以存儲一個或者多個字符。例如:String=“we are neuer”。
2 char是基本數據類型,而String是個類,屬於引用數據類型。String類可以調用方法,具有面向對象的特徵。
3 char在java中佔2個字節,16位。
2.String類詳解
1.創建方式兩種:一種是使用雙引號,一種是調用String類的構造函數
String x="abc"
String y = new String("abcd")
內存的分配情況。如圖:
Java爲String類型提供了緩衝池機制,當使用雙引號定義對象時,Java環境首先去字符串緩衝池尋找內容相同的字符串,如果存在就拿出來使用,否則就創建一個新的字符串放在緩衝池中。
採用new關鍵字新建一個字符串對象時,JVM首先在字符串池中查找有沒有"abcd"這個字符串對象,
如果有,則不在池中再去創建"abcd"這個對象了,直接在堆中創建一個"abcd"字符串對象,然後將堆中的這個"abcd"對象的地址返回賦給引用 y,這樣,y就指向了堆中創建的這個"abcd"字符串對象;
如果沒有,則首先在字符串池中創建一個"abcd"字符串對象,然後再在堆中創建一個"abcd"字符串對象,然後將堆中這個"abcd"字符串對象的地址返回賦給y引用,這樣,y指向了堆中創建的這個"abcd"字符串對象。
注意:由於String字符串的不可變性我們可以十分肯定常量池中一定不存在兩個相同的字符串
3.舉例驗證
eg:
public viod test{
String str1="aaa";
String str2="aaa";
System.out.println(str1==str2);
}
public static void test2() {
String str3 = new String("aaa");
String str4 = new String("aaa");
System.out.println("===========test2============");
System.out.println(str3 == str4);//false 可以看出用new的方式是生成不同的對象
}
結果:true,false
test中str1與str2都指向的是常量池中的同一個對象所以是true
test2中,採用new關鍵字創建對象時,每次new出來的都是一個新的對象,str3與str4分別指向堆內存中不同的對象,並且常量池中保存了一份“aaa”,所以是false
/**
* 編譯期確定
*/
public void test3(){
String s0="helloworld";
String s1="helloworld";
String s2="hello"+"world";
System.out.println("===========test3============");
System.out.println(s0==s1); //true 可以看出s0跟s1是指向同一個對象
System.out.println(s0==s2); //true 可以看出s0跟s2是指向同一個對象
}
因爲例子中的s0和s1中的"helloworld”都是字符串常量,它們在編譯期就被確定了,所以s0==s1爲true;而"hello”和"world”也都是字符串常量,當一個字符串由多個字符串常量連接而成時,它自己肯定也是字符串常量,所以s2也同樣在編譯期就被解析爲一個字符串常量,所以s2也是常量池中"helloworld”的一個引用。所以我們得出s0==s1==s2。
/**
* 編譯期無法確定
*/
public void test4(){
String s0="helloworld";
String s1=new String("helloworld");
String s2="hello" + new String("world");
System.out.println("===========test4============");
System.out.println( s0==s1 ); //false
System.out.println( s0==s2 ); //false
System.out.println( s1==s2 ); //false
}
用new String() 創建的字符串不是常量,不能在編譯期就確定,所以new String() 創建的字符串不放入常量池中,它們有自己的地址空間
s0還是常量池中"helloworld”的引用,s1因爲無法在編譯期確定,所以是運行時創建的新對象"helloworld”的引用,s2因爲有後半部分new String(”world”)所以也無法在編譯期確定,所以也是一個新創建對象"helloworld”的引用。
/*
* 編譯期無法確定
*/
public void test7(){
String s0 = "ab";
String s1 = "b";
String s2 = "a" + s1;
System.out.println("===========test7============");
System.out.println((s0 == s2)); //result = false
}
JVM對於字符串引用,由於在字符串的"+"連接中,有字符串引用存在,而引用的值在程序編譯期是無法確定的,即"a" + s1無法被編譯器優化,只有在程序運行期來動態分配並將連接後的新地址賦給s2。所以上面程序的結果也就爲false。
**
* 繼續-編譯期無法確定
*/
public void test5(){
String str1="abc";
String str2="def";
String str3=str1+str2;
System.out.println("===========test5============");
System.out.println(str3=="abcdef"); //false
}
因爲str3指向堆中的"abcdef"對象,而"abcdef"是字符串池中的對象,所以結果爲false。JVM對String str="abc"對象放在常量池中是在編譯時做的,而String str3=str1+str2是在運行時刻才能知道的。new對象也是在運行時才做的。而這段代碼總共創建了5個對象,字符串池中兩個、堆中三個。+運算符會在堆中建立來兩個String對象,這兩個對象的值分別是"abc"和"def",也就是說從字符串池中複製這兩個值,然後在堆中創建兩個對象,然後再建立對象str3,然後將"abcdef"的堆地址賦給str3。
步驟:
1)棧中開闢一塊中間存放引用str1,str1指向池中String常量"abc"。
2)棧中開闢一塊中間存放引用str2,str2指向池中String常量"def"。
3)棧中開闢一塊中間存放引用str3。
4)str1 + str2通過StringBuilder的最後一步toString()方法還原一個新的String對象"abcdef",因此堆中開闢一塊空間存放此對象。
5)引用str3指向堆中(str1 + str2)所還原的新String對象。
6)str3指向的對象在堆中,而常量"abcdef"在池中,輸出爲false。
/**
* 比較字符串常量的“+”和字符串引用的“+”的區別
*/
public void test8(){
String test="javalanguagespecification";
String str="java";
String str1="language";
String str2="specification";
System.out.println("===========test8============");
System.out.println(test == "java" + "language" + "specification");
System.out.println(test == str + str1 + str2);
}
執行上述代碼,結果爲:true、false。
分析:爲什麼出現上面的結果呢?這是因爲,字符串字面量拼接操作是在Java編譯器編譯期間就執行了,也就是說編譯器編譯時,直接把"java"、"language"和"specification"這三個字面量進行"+"操作得到一個"javalanguagespecification" 常量,並且直接將這個常量放入字符串池中,這樣做實際上是一種優化,將3個字面量合成一個,避免了創建多餘的字符串對象。而字符串引用的"+"運算是在Java運行期間執行的,即str + str2 + str3在程序執行期間纔會進行計算,它會在堆內存中重新創建一個拼接後的字符串對象。總結來說就是:字面量"+"拼接是在編譯期間進行的,拼接後的字符串存放在字符串池中;而字符串引用的"+"拼接運算實在運行時進行的,新創建的字符串存放在堆中。
對於直接相加字符串,效率很高,因爲在編譯器便確定了它的值,也就是說形如"I"+"love"+"java"; 的字符串相加,在編譯期間便被優化成了"Ilovejava"。對於間接相加(即包含字符串引用),形如s1+s2+s3; 效率要比直接相加低,因爲在編譯器不會對引用變量進行優化。
public void test9(){
String s0 = "ab";
final String s1 = "b";
String s2 = "a" + s1;
System.out.println("===========test9============");
System.out.println((s0 == s2)); //result = true
}
s1字符串加了final修飾,對於final修飾的變量,它在編譯時被解析爲常量值的一個本地拷貝存儲到自己的常量池中或嵌入到它的字節碼流中。所以此時的"a" + s1和"a" + "b"效果是一樣的。故上面程序的結果爲true。
/**
* 編譯期無法確定
*/
public void test10(){
String s0 = "ab";
final String s1 = getS1();
String s2 = "a" + s1;
System.out.println("===========test10============");
System.out.println((s0 == s2)); //result = false
}
private static String getS1() {
return "b";
}
這裏面雖然將s1用final修飾了,但是由於其賦值是通過方法調用返回的,那麼它的值只能在運行期間確定,因此s0和s2指向的不是同一個對象,故上面程序的結果爲false。
總結:
(1)單獨使用""引號創建的字符串都是常量,編譯期就已經確定存儲到String Pool中;
(2)使用new String("")創建的對象會存儲到heap中,是運行期新創建的;
new創建字符串時首先查看池中是否有相同值的字符串,如果有,則拷貝一份到堆中,然後返回堆中的地址;如果池中沒有,則在堆中創建一份,然後返回堆中的地址
(3)使用只包含常量的字符串連接符如"aa" + "aa"創建的也是常量,編譯期就能確定,已經確定存儲到String Pool中;
(4)使用包含變量的字符串連接符如"aa" + s1創建的對象是運行期才創建的,存儲在heap中;
4.intern方法
String 有一個intern() 方法,native,用來檢測在String pool是否已經有這個String存在。
public String intern()
返回字符串對象的規範化表示形式。
一個初始時爲空的字符串池,它由類 String 私有地維護。
當調用 intern 方法時,如果池已經包含一個等於此 String 對象的字符串(該對象由 equals(Object) 方法確定),則返回池中的字符串。否則,將此 String 對象添加到池中,並且返回此池中 String 對象的引用。
它遵循對於任何兩個字符串 s 和 t,當且僅當 s.equals(t) 爲 true 時,s.intern() == t.intern() 才爲 true。
所有字面值字符串和字符串賦值常量表達式都是內部的。
返回:
一個字符串,內容與此字符串相同,但它保證來自字符串池中。
public static void test5(){
String s2 = new String("abcd");
String s1 = "abcd";
String s3 = s2.intern();
String s4 = s1.intern();
System.out.println(s2==s3);
System.out.println(s1==s4);
System.out.println(s3==s4);
}
false
true
true
5.關於equals和==
(1)對於==,如果作用於基本數據類型的變量(byte,short,char,int,long,float,double,boolean ),則直接比較其存儲的"值"是否相等;如果作用於引用類型的變量(String),則比較的是所指向的對象的地址(即是否指向同一個對象)。
(2)equals方法是基類Object中的方法,因此對於所有的繼承於Object的類都會有該方法。在Object類中,equals方法是用來比較兩個對象的引用是否相等,即是否指向同一個對象。
(3)對於equals方法,注意:equals方法不能作用於基本數據類型的變量。如果沒有對equals方法進行重寫,則比較的是引用類型的變量所指向的對象的地址;而String類對equals方法進行了重寫,用來比較指向的字符串對象所存儲的字符串是否相等。其他的一些類諸如Double,Date,Integer等,都對equals方法進行了重寫用來比較指向的對象所存儲的內容是否相等。
6. .String、StringBuffer、StringBuilder的區別
(1)可變與不可變:String是不可變字符串對象,StringBuilder和StringBuffer是可變字符串對象(其內部的字符數組長度可變)。
(2)是否多線程安全:String中的對象是不可變的,也就可以理解爲常量,顯然線程安全。StringBuffer 與 StringBuilder 中的方法和功能完全是等價的,只是StringBuffer 中的方法大都採用了synchronized 關鍵字進行修飾,因此是線程安全的,而 StringBuilder 沒有這個修飾,可以被認爲是非線程安全的。
(3)String、StringBuilder、StringBuffer三者的執行效率:
StringBuilder > StringBuffer > String 當然這個是相對的,不一定在所有情況下都是這樣。比如String str = "hello"+ "world"的效率就比 StringBuilder st = new StringBuilder().append("hello").append("world")要高。因此,這三個類是各有利弊,應當根據不同的情況來進行選擇使用:
當字符串相加操作或者改動較少的情況下,建議使用 String str="hello"這種形式;
當字符串相加操作較多的情況下,建議使用StringBuilder,如果採用了多線程,則使用StringBuffer。