什麼是字符串常量池

在理解字符串常量前,咱們先熟悉一下如何建立一個字符串,在Java中有兩種方法能夠建立一個字符串對象:java

  • 使用new運算符。例如:express

1
String str = new String( "Hello" );
  • 使用字符串常量或者常量表達式。例如:app

1
2
String str= "Hello" ; //(字符串常量) 或者
String str= "Hel" + "lo" ; //(字符串常量表達式).

這些字符串的建立方式之間有什麼區別呢?在Java中,equals方法被認爲是對象的值進行深層次的比較,而操做符==是進行的淺層次的比較。equals方法比較兩個對象的內容而不是引用。==兩側是引用類型(例如對象)時,若是引用是相同的-即指向同一個對象-則執行結果爲真。若是是值類型(例如原生類型),若是值相同,則執行結果爲真。equals方法在兩個對象具備相同內容時返回真-可是,java.lang.Object類中的equals方法返回真-若是類沒有覆蓋默認的equals方法,若是兩個引用指向同一個對象。性能

讓咱們經過下面的例子來看看這兩種字符串的建立方式之間有什麼區別吧。優化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
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)); 
     }
}

輸出結果爲:ui

1
2
3
4
5
6
7
8
9
10
11
12
13
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節約資源的一種方式。spa

字符串常量池

字符串的分配,和其餘的對象分配同樣,耗費高昂的時間與空間代價。JVM爲了提升性能和減小內存開銷,在實例化字符串常量的時候進行了一些優化。爲了減小在JVM中建立的字符串的數量,字符串類維護了一個字符串池,每當代碼建立字符串常量時,JVM會首先檢查字符串常量池。若是字符串已經存在池中,就返回池中的實例引用。若是字符串不在池中,就會實例化一個字符串並放到池中。Java可以進行這樣的優化是由於字符串是不可變的,能夠不用擔憂數據衝突進行共享。例如:指針

1
2
3
4
5
6
7
8
9
public class Program
{
     public static void main(String[] args)
     {
        String str1 = "Hello"
        String str2 = "Hello" ;
        System.out.print(str1 == str2);
     }
}

其結果是:code

1
true

不幸的是,當使用:orm

1
String a= new String( "Hello" );

一個字符串對象在字符串常量池外建立,即便池裏存在相同的字符串。考慮到這些,要避免new一個字符串除非你明確的知道須要這麼作!例如:

1
2
3
4
5
6
7
8
9
10
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));
     }
}

結果是:

1
false true

JVM中有一個常量池,任何字符串至多維護一個對象。字符串常量老是指向字符串池中的一個對象。經過new操做符建立的字符串對象不指向字符串池中的任何對象,可是能夠經過使用字符串的intern()方法來指向其中的某一個。java.lang.String.intern()返回一個保留池字符串,就是一個在全局字符串池中有了一個入口。若是之前沒有在全局字符串池中,那麼它就會被添加到裏面。例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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));
     }
}

輸出是:

1
2
s1 == s2? false
s1 == s3? true

爲了優化空間,運行時實例建立的全局字符串常量池中有一個表,老是爲池中每一個惟一的字符串對象維護一個引用。這就意味着它們一直引用着字符串常量池中的對象,因此,在常量池中的這些字符串不會被垃圾收集器回收。

Java語言規範第三版中的字符串常量

每個字符串常量都是指向一個字符串類實例的引用。字符串對象有一個固定值。字符串常量,或者通常的說,常量表達式中的字符串都被使用方法 String.intern進行保留來共享惟一的實例。

1
2
3
4
5
6
7
8
9
10
11
12
13
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" ; }

編譯單元:

1
2
package other;
public class Other { static String hello = "Hello" ; }

產生輸出:

1
true true true true false true

這個例子說明了六點:

  • 同一個包下同一個類中的字符串常量的引用指向同一個字符串對象;

  • 同一個包下不一樣的類中的字符串常量的引用指向同一個字符串對象;

  • 不一樣的包下不一樣的類中的字符串常量的引用仍然指向同一個字符串對象;

  • 由常量表達式計算出的字符串在編譯時進行計算,而後被看成常量;

  • 在運行時經過鏈接計算出的字符串是新建立的,所以是不一樣的;

  • 經過計算生成的字符串顯示調用intern方法後產生的結果與原來存在的一樣內容的字符串常量是同樣的。

相關文章
相關標籤/搜索