什麼是字串常量池?
在理解字串常量前,我們先熟悉一下如何建立一個字串,在Java中有兩種方法可以建立一個字串物件:
- 使用new運算子。例如:
String str = new String("Hello");
- 使用字串常量或者常量表達式。例如:
String str="Hello"; //(字串常量) 或者
String str="Hel" + "lo"; //(字串常量表達式).
這些字串的建立方式之間有什麼區別呢?在Java中,equals方法被認為是物件的值進行深層次的比較,而操作符==是進行的淺層次的比較。equals方法比較兩個物件的內容而不是引用。==兩側是引用型別(例如物件)時,如果引用是相同的-即指向同一個物件-則執行結果為真。如果是值型別(例如原生型別),如果值相同,則執行結果為真。equals方法在兩個物件具有相同內容時返回真-但是,java.lang.Object類中的equals方法返回真-如果類沒有覆蓋預設的equals方法,如果兩個引用指向同一個物件。
讓我們通過下面的例子來看看這兩種字串的建立方式之間有什麼區別吧。
public class DemoStringCreation { public static void main(String args[]) { String str1 = "Hello"; String str2 = "Hello"; System.out.println("str1 and str2 are created by using string literal."); System.out.println(" str1 == str2 is " + (str1 == str2)); System.out.println(" str1.equals(str2) is " + str1.equals(str2)); String str3 = new String("Hello"); String str4 = new String("Hello"); System.out.println("str3 and str4 are created by using new operator."); System.out.println(" str3 == str4 is " + (str3 == str4)); System.out.println(" str3.equals(str4) is " + str3.equals(str4)); String str5 = "Hel" + "lo"; String str6 = "He" + "llo"; System.out.println("str5 and str6 are created by using string constant expression."); System.out.println(" str5 == str6 is " + (str5 == str6)); System.out.println(" str5.equals(str6) is " + str5.equals(str6)); String s = "lo"; String str7 = "Hel" + s; String str8 = "He" + "llo"; System.out.println("str7 is computed at runtime."); System.out.println("str8 is created by using string constant expression."); System.out.println(" str7 == str8 is " + (str7 == str8)); System.out.println(" str7.equals(str8) is " + str7.equals(str8)); } }
輸出結果為:
str1 and str2 are created by using string literal. str1 == str2 is true str1.equals(str2) is true str3 and str4 are created by using new operator. str3 == str4 is false str3.equals(str4) is true str5 and str6 are created by using string constant expression. str5 == str6 is true str5.equals(str6) is true str7 is computed at runtime. str8 is created by using string constant expression. str7 == str8 is false str7.equals(str8) is true
使用相同的字元序列而不是使用new關鍵字建立的兩個字串會建立指向Java字串常量池中的同一個字串的指標。字串常量池是Java節約資源的一種方式。
字串常量池
字串的分配,和其他的物件分配一樣,耗費高昂的時間與空間代價。JVM為了提高效能和減少記憶體開銷,在例項化字串常量的時候進行了一些優化。為了減少在JVM中建立的字串的數量,字串類維護了一個字串池,每當程式碼建立字串常量時,JVM會首先檢查字串常量池。如果字串已經存在池中,就返回池中的例項引用。如果字串不在池中,就會例項化一個字串並放到池中。Java能夠進行這樣的優化是因為字串是不可變的,可以不用擔心資料衝突進行共享。例如:
public class Program
{
public static void main(String[] args)
{
String str1 = "Hello";
String str2 = "Hello";
System.out.print(str1 == str2);
}
}
其結果是:
true
不幸的是,當使用:
String a=new String("Hello");
一個字串物件在字串常量池外建立,即使池裡存在相同的字串。考慮到這些,要避免new一個字串除非你明確的知道需要這麼做!例如:
public class Program
{
public static void main(String[] args)
{
String str1 = "Hello";
String str2 = new String("Hello");
System.out.print(str1 == str2 + " ");
System.out.print(str1.equals(str2));
}
}
結果是:
false true
JVM中有一個常量池,任何字串至多維護一個物件。字串常量總是指向字串池中的一個物件。通過new操作符建立的字串物件不指向字串池中的任何物件,但是可以通過使用字串的intern()方法來指向其中的某一個。java.lang.String.intern()返回一個保留池字串,就是一個在全域性字串池中有了一個入口。如果以前沒有在全域性字串池中,那麼它就會被新增到裡面。例如:public class Program
{
public static void main(String[] args)
{
// Create three strings in three different ways.
String s1 = "Hello";
String s2 = new StringBuffer("He").append("llo").toString();
String s3 = s2.intern();
// Determine which strings are equivalent using the ==
// operator
System.out.println("s1 == s2? " + (s1 == s2));
System.out.println("s1 == s3? " + (s1 == s3));
}
}
輸出是:
s1 == s2? false
s1 == s3? true
為了優化空間,執行時例項建立的全域性字串常量池中有一個表,總是為池中每個唯一的字串物件維護一個引用。這就意味著它們一直引用著字串常量池中的物件,所以,在常量池中的這些字串不會被垃圾收集器回收。
Java語言規範第三版中的字串常量
每一個字串常量都是指向一個字串類例項的引用。字串物件有一個固定值。字串常量,或者一般的說,常量表達式中的字串都被使用方法 String.intern進行保留來共享唯一的例項。
package testPackage;
class Test {
public static void main(String[] args) {
String hello = "Hello", lo = "lo";
System.out.print((hello == "Hello") + " ");
System.out.print((Other.hello == hello) + " ");
System.out.print((other.Other.hello == hello) + " ");
System.out.print((hello == ("Hel"+"lo")) + " ");
System.out.print((hello == ("Hel"+lo)) + " ");
System.out.println(hello == ("Hel"+lo).intern());
}
}
class Other { static String hello = "Hello"; }
編譯單元:package other;
public class Other { static String hello = "Hello"; }
產生輸出:
true true true true false true
這個例子說明了六點:
- 同一個包下同一個類中的字串常量的引用指向同一個字串物件;
- 同一個包下不同的類中的字串常量的引用指向同一個字串物件;
- 不同的包下不同的類中的字串常量的引用仍然指向同一個字串物件;
- 由常量表達式計算出的字串在編譯時進行計算,然後被當作常量;
- 在執行時通過連線計算出的字串是新建立的,因此是不同的;
- 通過計算生成的字串顯示呼叫intern方法後產生的結果與原來存在的同樣內容的字串常量是一樣的。