對於java的學習者而言,無論是初學者,還是java大師,String對於大家而言,也絕對不會陌生。下面本人就從
自己學習的角度大致分析一下String,StringBuffer和StringBuilder這三者的區別和聯系。如有不足,歡迎補充說
明~謝謝
1 String類
String類在java的java.lang.String包下面,需要特別說明的是String類是final關鍵字修飾的,也就是說String類是不能夠被繼承的,String中的內容一旦被創建后是不能被修改的。Stirng是對象也不是8種基本數據類型
1) 具體的講解請看下面的例子:
package com.yonyou.test; class Test{ public static void main(String[] args) { String str=new String("龍不吟"); str=new String("虎不嘯");//原始String對象中str的內容到底變了沒有? System.out.println(str); //下面也是一個String的例子 String str2="天下太平"; str2=str2+"國泰民安";//原始String對象中的str2到底變了沒有? System.out.println(str2); } }
首先說明上述原始String對象中的內容都沒有改變!
對於這個問題大家可以這樣理解:
如果大家看過String的源碼可以發現,String字符串中的內容存儲在char數組中的。
在判斷原始String對象str和str2的是否改變了,這里需要明白一個問題,在java中相關對象的引用變量一般都存在棧中,而相關的對象都是存在堆中的,棧中的值指向了它所引用的對象(堆中相應的對象的地址)。
棧 :由JVM分配區域,用於保存線程執行的動作和數據引用。棧是一個運行的單位,Java中一個線程就會相應有一個線程棧與之對應。
一般是用於存儲的基本數據類型的局部變量(注意這里僅僅是局部的,對於全局變量不能這樣定義哦?)
堆 :由JVM分配的,用於存儲對象等數據的區域。一般用於存儲我們new出來的對象。
常量池 :在堆中分配出來的一塊存儲區域,用於存儲顯式 的String,float或者integer.例如String str="abc"; abc這個字符串是顯式聲明,所以存儲在常量池。
對於java內存的更詳細的講解請參考:http://www.cnblogs.com/xiohao/p/4278173.html 這里不再累述。
例如:
創建一個對象String str=new String("Hello World");
對於變量str而言,它代表的是引用變量,它的值是存儲在棧中的,而new String("Hello World")會創建一個新的對象,而對象的值是存儲在堆中的。而引用
變量str指向對中的對象new String("Hello World"); 這樣看來視乎上面的問題就很好解釋了。
由於是String修飾的str和str2而言,它們的引用變量的本身是不能夠改變,但是它們指向的對象,比如說指向的堆中地址卻是可以改變的。
所以說上面String對象str和str2所對應的原始對象都沒有改變,僅僅是str和str2所對應的引用變量的指向發生的改變。這段話有一些繞
理解起來不是那么容易,請多讀幾遍,反復思考一下。
2) 接下來大家可以來理解一下
package com.yonyou.test; class Test{ public static void main(String[] args) { String str=new String("Hello World"); String str2="Hello World"; System.out.println("str和str2的equals值相同嗎?"+str.equals(str2)); System.out.println("str和str2的==值相同嗎?"+(str==str2));//注意此處的str==str2必須用括號括起來, // 否則的話 字符連接符號 +的優先級高於==,實際上進行的比較是 //str和str2的==值相同嗎?Hello World和Hello World是否相同 } }
輸出結果為:
str和str2的equals值相同嗎?true
str和str2的==值相同嗎?false
這些結果右是怎么輸出來的呢?
首先我們需要明白equals和==的區別和聯系
對於equals而言,它是 Object類中方法如下:
...
* @param obj the reference object with which to compare.
* @return <code>true</code> if this object is the same as the obj
* argument; <code>false</code> otherwise.
* @see #hashCode()
* @see java.util.Hashtable
*/
public boolean equals(Object obj) {
return (this == obj);
}
通過在這里查看Object類中equals的方法我們知道,如果一個類沒有重寫Object類中的equals方法的話,那么它的作用和==的作用是一樣的。說白了是
沒有任何區別的。但是如果用戶可以根據自己的需求進行重寫equals方法那樣的話,equals比較的返回值就和相關的需求相關了。
如在jdk中的String類中的equals方法是這樣重寫的:
/** * Compares this string to the specified object. The result is {@code * true} if and only if the argument is not {@code null} and is a {@code * String} object that represents the same sequence of characters as this * object. * * @param anObject * The object to compare this {@code String} against * * @return {@code true} if the given object represents a {@code String} * equivalent to this string, {@code false} otherwise * * @see #compareTo(String) * @see #equalsIgnoreCase(String) */ public boolean equals(Object anObject) { if (this == anObject) { return true; } if (anObject instanceof String) { String anotherString = (String)anObject; int n = count; if (n == anotherString.count) { char v1[] = value; char v2[] = anotherString.value; int i = offset; int j = anotherString.offset; while (n-- != 0) { if (v1[i++] != v2[j++]) return false; } return true; } } return false; }
這樣我們就可以發現,對於String對象中的equals方法而言,它的目的是比較兩個對象所對應的值是否相同,注意僅僅是兩個對象的值,跟這兩個對象
的引用(地址沒有任何關系)。
而對於==而言,它在java中的主要作用則是用來比較java中一些基本類型(如int,long,float等)的值是否相同以及比較兩個對象是否有相同(即兩個對象的引用
地址是否相同)。
這也就明白了為什么上面的對象equals的值相同而==的值不同。
3)對於下面聲明的這個變量創建了幾個對象?
String str=new Stirng("xyz"); //創建了幾個對象
String str2="abc";//創建了幾個對象
首先說“String str=new Stirng("xyz");”創建了一個或者兩個。
對於“xyz”這個對象而言,它是存放在字符串的緩沖區中的,不管出現多少遍,都是緩沖區中的那一個。而new String()每次都會創建一個新的對象。所以如果之前創建
過“xyz”這個對象的話,那么久創建一個對象,而如果之前要是沒有創建過這個字符串的話,那么就會創建兩個對象。
其次說String str2=“abc”會創建零個或者一個。
這個是為什么我就不哆嗦了。
需要注意str和str2是變量名不是對象。
請看下面的這條語句創建了幾個對象?
String str="a"+"b"+"c"+"d"+"e"+"f"+"g"+"h"+"i"+"j"+"k";
沒錯這里僅僅創建了一個對象即str=”abcdefghijk“;
為了更好的說明這個問題我們來看下面的例子:
package com.xiaohao.test; public class Test{ public static void main(String[] args) { String str1="ab"; String str2="a"+"b"; String str3="b"; String str4="a"+str3; System.out.println("str1和str2相等嗎?"+(str1==str2)); System.out.println("str1和str4相等嗎?"+(str1==str4)); } }
上面程序的輸出結果為:
str1和str2相等嗎?true
str1和str4相等嗎?false
這說明javac編譯的時候可以對字符串常量直接相加的表達式進行優化,不必等到運行期在進行加法處理,而是在編譯的時候直接去掉加號,直接將其編譯成這些常量
相連的結果。而對於str4而言由於str3是變量,不是字符串常量,所以最終的結果為false。
下面來講StringBuffer和StringBuilder
對於StringBuffer和StringBuilder是對Stirng的一個優化。
之前已經說過了,String對象一旦創建后,String中的內容是不能夠改變的。每次改變String都會創建新的對象。這樣的話會造成相應空間的浪費。介於此jdk額開
發人員計出了StringBuffer和StringBuilder,對於后者而言它們的內容是能夠動態改變的。而StringBuffer和StringBuilder的區別就在於StringBuffer是線程安全的
(StringBuffer中的絕大部分方法都加了同步關鍵字)而StringBuilder是線程不安全的。因為StringBuilder是線程不安全的,所以其運行效率會更高,如果一個字符串
是在方法里面定義的話,這種情況僅僅可能有一個線程訪問它,不存在不安全的因素,這時推薦使用StringBuilder。如果一個實例變量是在類里面定義的,並且在多線程
下會訪問到,這時最好使用StringBuffer
為了更好的理解StringBuffer和StringBuilder的效率問題,請看下面的例子:
package com.xiaohao.test; public class Test2 { public static void main(String[] args) { String str=new String(); StringBuffer sb1=new StringBuffer(); StringBuilder sb2=new StringBuilder(); long startTime=System.currentTimeMillis(); for(int i=0;i<100000;i++) { str=str+i; } long endTime=System.currentTimeMillis(); System.out.println("Stirng消耗的時間為:"+(endTime-startTime)); startTime=System.currentTimeMillis(); for(int i=0;i<100000;i++) { sb2.append(i); } endTime=System.currentTimeMillis(); System.out.println("StirngBuilder消耗的時間為:"+(endTime-startTime)); startTime=System.currentTimeMillis(); for(int i=0;i<100000;i++) { sb1.append(i); } endTime=System.currentTimeMillis(); System.out.println("StirngBuffer消耗的時間為:"+(endTime-startTime)); } }
運行結果為:
Stirng消耗的時間為:42185
StirngBuilder消耗的時間為:0
StirngBuffer消耗的時間為:0
相關效率,你懂的~~~
另外StringBuffer和StringBuilder沒有重寫equals和hashcode方法,它們在存儲在java集合框架的時候可能出現問題。
好吧,就先到這里吧。