字符串在JVM中如何存放 及常量池技術

字符串對象在JVM中可能有兩個存放的位置:字符串常量池或堆內存

  • 使用常量字符串初始化的字符串對象,它的值存放在字符串常量池中;

  • 使用字符串構造方法創建的字符串對象,它的值存放在堆內存中;

String提供了一個API, java.lang.String.intern(),這個API可以手動將一個字符串對象的值轉移到字符串常量池中。

在1.7之前,字符串常量池是在PermGen區域,這個區域的大小是固定的——不能在運行時根據需要擴大,也不能被垃圾收集器回收,因此如果程序中有太多的字符串調用了intern方法的話,就可能造成OOM。

在1.7以後,字符串常量池移到了堆內存中,並且可以被垃圾收集器回收,這個改動降低了字符串常量池OOM的風險。

案例分析

1. String對象的兩種創建方式:

           String s1="javaadu";

           String s2="javaadu";

           String s3=new String("javaadu");

           System.out.println(s1==s2);          //true     

           System.out.println(s1==s3);          //false

           

           String s4=s3.intern();

           System.out.println(s1==s4);          //true

           System.out.println(s3==s4);          //false

  • 直接使用雙引號聲明出來的String對象會直接存儲在常量池中。

  • 如果不是用雙引號聲明的String對象,可以使用String提供的intern方法。String.intern() 是一個Native方法,它的作用是:如果常量池中已經包含一個等於此String對象內容的字符串,則返回常量池中該字符串的引用;如果沒有,則在常量池中創建與此 String 內容相同的字符串,並返回常量池中創建的字符串的引用。

2. String字符串拼接

           String str1 = "str";

           String str2 = "ing";

           String str3 ="str"+"ing";  //常量池中的對象

           String str4 = str1 + str2;  //在堆上創建的新對象       

           String str5 = "string";        

           System.out.println(str3 == str4);          //flase

           System.out.println(str3 == str5);          //true

           System.out.println(str4 == str5);          //false

String s1 = new String("abc");這句話創建了幾個對象?

創建了兩個對象。或者一個

如果如果常量池沒有,就是兩個;如果常量池已經有,就是一個。

           String str1=new String("abc");

           String str2="abc";

           System.out.println(str1==str2);      //false

解釋:

先有字符串"abc"放入常量池,然後 new 了一份字符串"abc"放入Java堆(字符串常量"abc"在編譯期就已經確定放入常量池,而 Java 堆上的"abc"是在運行期初始化階段才確定),然後 Java 棧的 str1 指向Java堆上的"abc"。

3. 8種基本類型的包裝類和常量池

  • Java 基本類型的包裝類的大部分都實現了常量池技術,即Byte,Short,Integer,Long,Character,Boolean;這5種包裝類默認創建了數值[-128,127]的相應類型的緩存數據,但是超出此範圍仍然會去創建新的對象。

  • 兩種浮點數類型的包裝類 Float,Double 並沒有實現常量池技術。

           Integer aInteger=1;

           Integer bInteger=2;

           

           Integer cInteger=3;

           Integer dInteger=3;

           

           Integer eInteger=321;

           Integer fInteger=321;

           

           Long gLong=3L;

           Long hLong=2L;

           

           System.out.println(cInteger==dInteger);         //true

           System.out.println(eInteger==fInteger);         //false

           System.out.println(cInteger==(aInteger+bInteger));        //true

           System.out.println(cInteger.equals(aInteger+bInteger)); //true

           System.out.println(gLong==(aInteger+bInteger));           //true

           System.out.println(gLong.equals(aInteger+bInteger)); //false

           System.out.println(gLong.equals(aInteger+hLong));         //true

使用==:

①如果比較Integer變量,默認比較的是地址值。

②Java的Integer維護了從-128~127的緩存池。

③如果比較的某一邊有操作表達式(如a+b),那麼比較的是具體數值。

使用equals():

①無論Integer還是Long中的equals默認比較的是數值。

②Long的equals()方法,JDK的默認實現:會判斷是否是Long類型。

           int a=59;       //基本類型,存儲在棧中。

           Integer bInteger=59; //會調用Integer的valueOf方法

           /*

            * valueOf這個方法就是返回一個Integer對象,返回之前,需要判斷當前值是否在[-128,127]區間。

            * 如果在此區間,先看IntegerCache中是否存在此對象,若存在,直接返回引用,否則創建一個新的對象。

            * 若不在此區間,return new Integer(i);

            */

           Integer cInteger=Integer.valueOf(59); //因爲IntegerCache中存在此對象,所以直接返回引用。

           Integer dInteger=new Integer(59);    //直接創建一個新的對象。

           

           System.out.println(a==bInteger);           //true    Integer會自動拆箱成int,然後進行值比較

           System.out.println(bInteger==cInteger);         //true

           System.out.println(cInteger==dInteger);         //false

           System.out.println(a==dInteger);           //true  dInteger會自動拆箱,進行值比較

注意:

==比較的是地址。

但當爲基本類型時,比較的是值。

如果兩邊有包裝類型,則先將包裝類型轉換爲基本類型再比較值是否相等。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章