String str1= new String("Java");
String str2= "Java";
由於 str1 與 str2 是兩個不同的物件,當然他的記憶體位址也會不同。所以當我用比較運算子 (==)來比較 str1 與 str2 時,因為比較運算子是比較記憶體位址,可以如期得到 false 的結果。
String str2= "Java";
str1 == str2 /* 回傳 false */
可是,當我另外宣告一個如同 str2 的變數,再去比較一次:
String str2= "Java";
String str3= "Java";
str2 == str3 /* 回傳 true */
這 str2 與 str3 不是兩個不同物件嗎? 為何用比較運算子去比較時,竟然會出現相等的結果?原來,這裡面還有一小段故事啊!在 「JAVA SE6 全方位學習」一書有提到:String str3= "Java";
str2 == str3 /* 回傳 true */
Java 為了加快程式的執行速度,又因為字串是每個程式中一定會用到的物件,所以 Java 設計了兩種不同的方式來產生字串物件,一種是呼叫 String 類別的建構子,另一種是使用雙引號【"】。這兩種方式產生出來的字串物件,它們在記憶體中存放的方式不一樣;使用建構子的方式所產生出來的物件,自己有自己的獨立空間,而之所以會有雙引號的方式,主要是為了加快程式執行的速度,所以 Java 會把這類的字串放在一個字串池(String Pool)之中,當用雙引號產生字串物件時,電腦會先去字串池中尋找有沒有相同的字串已經放在裡面了,如果有就直接拿出來用,如果沒有就產生一個新的字串放到字串池中。
當我們看到 Java 為了程式效能的提升,在 String 物件做了如上的調整,那 .Net 陣營的 C# 呢?答案是肯定的。
我把剛剛在 Java 的程式移植到 .Net 平台,改成如下:
String str1=new String("ASP.Net".ToCharArray());
String str2 = "ASP.Net";
String str3 = "ASP.Net";
Console.WriteLine(string.Format("str1==str2 {0}", (str1==str2).ToString()));
/* 回傳 True */
Console.WriteLine(string.Format("str2==str3 {0}", (str2 == str3).ToString()));
/* 回傳 True */
String str2 = "ASP.Net";
String str3 = "ASP.Net";
Console.WriteLine(string.Format("str1==str2 {0}", (str1==str2).ToString()));
/* 回傳 True */
Console.WriteLine(string.Format("str2==str3 {0}", (str2 == str3).ToString()));
/* 回傳 True */
有了先前在 Java 的 String Pool 概念後, str2==str3 回傳 True 已經可以理解,但為何 str1==str2 卻是 True 呢?比較運算子不是去比記憶體位址的嗎?
這當中的曲折,又要用另一個故事來說明了:
參考了「C# - Equals和等於等於( 等於比較運算式 )」這篇文章所引述 MSDN 的一段話:
對於預先定義的實值型別 (Value Type),等號比較運算子 (==) 在運算元相等時傳回 true;否則傳回 false。 對於 string 以外的參考型別,若兩個運算元參考到同一物件,== 會傳回 true。 對於 string 型別,== 會比較字串的值。
原來在 C# ,如果是屬於字串型別,比較運算子會去比較存放在記憶體裡面的值而不是記憶體位址,這一點的確是還蠻細的。
換言之,如果把剛剛 C# 的程式,把原先的字串型別都 Boxing 成 Object 型別呢?
Object obj1 = new String("ASP.Net".ToCharArray());
Object obj2 = "ASP.Net";
Object obj3 = "ASP.Net";
Console.WriteLine(string.Format("obj1==obj2 {0}", (obj1 == obj2).ToString()));
/* 回傳 False */
Console.WriteLine(string.Format("obj2==obj3 {0}", (obj2 == obj3).ToString()));
/* 回傳 True */
改成用 Object 型別後,的確就跟 Java 所認知的概念是一樣了。
Object obj2 = "ASP.Net";
Object obj3 = "ASP.Net";
Console.WriteLine(string.Format("obj1==obj2 {0}", (obj1 == obj2).ToString()));
/* 回傳 False */
Console.WriteLine(string.Format("obj2==obj3 {0}", (obj2 == obj3).ToString()));
/* 回傳 True */
最後自己還留下個疑問,到底這樣大費周章的使用 String Pool ,到底可以佔到多少便宜啊?於是寫了兩個小程式比較一下,就讓他跑個 2千萬次的迴圈看看:
Java:
public class jstring02 { public static void main(String[] args) { System.out.println(new java.text.SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new java.util.Date())); String [] a_str= new String[20000000]; for(int i=0;i<20000000;i++) { a_str[i]=new String("ASP.Net"); } System.out.println(new java.text.SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new java.util.Date())); String [] b_str= new String[20000000]; for(int i=0;i<20000000;i++) { b_str[i]="ASP.Net"; } System.out.println(new java.text.SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new java.util.Date())); } }C#
class Program { static void Main(string[] args) { Console.WriteLine(string.Format("{0}", DateTime.Now.ToString())); String[] a_str = new String[20000000]; for (int i = 0; i < 20000000; i++) { a_str[i] = new String("ASP.Net".ToCharArray()); } Console.WriteLine(string.Format("{0}", DateTime.Now.ToString())); String[] b_str = new String[20000000]; for (int i = 0; i < 20000000; i++) { b_str[i] = "ASP.Net"; } Console.WriteLine(string.Format("{0}", DateTime.Now.ToString())); Console.ReadLine(); } }
在 Java 與 C# ,第一次的時間是起始時間,第二次是執行使用建構子方式後的時間,第三次則是執行完 String Pool 後的時間。有了數據上的比較,對於有沒有使用 String Pool 的效能差別,會比較有感覺些。
PS:自己程式功力很差,但主要重點是想凸顯是否有使用 String Pool 的感覺。
參考:
01:Java SE 6全方位學習
02:C# - Equals和等於等於( 等於比較運算式 )