關於String內存分配的深刻探討

public class Test { app

   

    public static final String MESSAGE="taobao"; 優化

   

    public static void main(String[] args) { spa

      

       String a = "tao"+"bao"; 設計

       String b = "tao"; 對象

       String c = "bao"; 內存

      

System.out.println(a==MESSAGE);    System.out.println( (b+c)==MESSAGE);  字符串

    } 編譯器

} string

對於這道題,考察的是對String類型的認識以及編譯器優化。JavaString不是基本類型,可是有些時候和基本類型差很少,如String b = "tao"能夠對變量直接賦值,而不用 new 一個對象(固然也能夠用 new)。因此String這個類型值得好好研究下。 編譯

Java中的變量和基本類型的值存放於棧內存,而new出來的對象自己存放於堆內存,指向對象的引用仍是存放在棧內存。例如以下的代碼:

int i=1;

    String s = new String("Hello World");

變量is以及1存放在棧內存,而s指向的對象」Hello World」存放於堆內存。

 

 

 

 

棧內存的一個特色是數據共享,這樣設計是爲了減少內存消耗,前面定義了i=1i1都在棧內存內,若是再定義一個j=1,此時將j放入棧內存,而後查找棧內存中是否有1,若是有則j指向1。若是再給j賦值2,則在棧內存中查找是否有2,若是沒有就在棧內存中放一個2,而後j指向2。也就是若是常量在棧內存中,就將變量指向該常量,若是沒有就在該棧內存增長一個該常量,並將變量指向該常量。

 

 

若是j++,這時指向的變量並不會改變,而是在棧內尋找新的常量(比原來的常量大1),若是棧內存有則指向它,若是沒有就在棧內存中加入此常量並將j指向它。這種基本類型之間比較大小和咱們邏輯上判斷大小是一致的。如定義ij是都賦值1,則i==j結果爲true==用於判斷兩個變量指向的地址是否同樣。i==j就是判斷i指向的1j指向的1是同一個嗎?固然是了。對於直接賦值的字符串常量(如String s=Hello World」;中的Hello World)也是存放在棧內存中,而new出來的字符串對象(即String對象)是存放在堆內存中。若是定義String s=Hello World」和String w=Hello World」,s==w嗎?確定是true,由於他們指向的是同一個Hello World

 

 

堆內存沒有數據共享的特色,前面定義的String s = new String("Hello World");後,變量s在棧內存內,Hello World 這個String對象在堆內存內。若是定義String w =new String("Hello World");,則會在堆內存建立一個新的String對象,變量w存放在棧內存,w指向這個新的String對象。堆內存中不一樣對象(指同一類型的不一樣對象)的比較若是用==則結果確定都是false,好比s==w?固然不等,sw指向堆內存中不一樣的String對象。若是判斷兩個String對象相等呢?用equals方法。

 

 

 

說了這麼多隻是說了這道題的鋪墊知識,還沒進入主題,下面分析這道題。MESSAGE成員變量及其指向的字符串常量確定都是在棧內存裏的,變量a運算完也是指向一個字符串「taobao」啊?是否是同一個呢?這涉及到編譯器優化問題。對於字符串常量的相加,在編譯時直接將字符串合併,而不是等到運行時再合併。也就是說

String a = "tao"+"bao";String a = "taobao";編譯出的字節碼是同樣的。因此等到運行時,根據上面說的棧內存是數據共享原則,aMESSAGE指向的是同一個字符串。而對於後面的(b+c)又是什麼狀況呢?b+c只能等到運行時才能斷定是什麼字符串,編譯器不會優化,想一想這也是有道理的,編譯器怕你對b的值改變,因此編譯器不會優化。運行時b+c計算出來的"taobao"和棧內存裏已經有的"taobao"是一個嗎?不是。b+c計算出來的"taobao"應該是放在堆內存中的String對象。這能夠經過System.out.println( (b+c)==MESSAGE);的結果爲false來證實這一點。若是計算出來的b+c也是在棧內存,那結果應該是trueJavaString的相加是經過StringBuffer實現的,先構造一個StringBuffer裏面存放」tao」,而後調用append()方法追加」bao」,而後將值爲」taobao」StringBuffer轉化成String對象。StringBuffer對象在堆內存中,那轉換成的String對象理所應當的也是在堆內存中。下面改造一下這個語句System.out.println( (b+c).intern()==MESSAGE);結果是trueintern()方法會先檢查String(或者說成棧內存)中是否存在相同的字符串常量,若是有就返回。因此intern()返回的就是MESSAGE指向的"taobao"。再把變量bc的定義改一下,

final String b = "tao";

        final String c = "bao";

            

       System.out.println( (b+c)==MESSAGE);

如今bc不可能再次賦值了,因此編譯器將b+c編譯成了」taobao」。所以,這時的結果是true

在字符串相加中,只要有一個是非final類型的變量,編譯器就不會優化,由於這樣的變量可能發生改變,因此編譯器不可能將這樣的變量替換成常量。例如將變量bfinal去掉,結果又變成了false。這也就意味着會用到StringBuffer對象,計算的結果在堆內存中。

    若是對指向堆內存中的對象的String變量調用intern()會怎麼樣呢?實際上這個問題已經說過了,(b+c).intern()b+c的結果就是在堆內存中。對於指向棧內存中字符串常量的變量調用intern()返回的仍是它本身,沒有多大意義。它會根據堆內存中對象的值,去查找String池中是否有相同的字符串,若是有就將變量指向這個string池中的變量。

String a = "tao"+"bao";

       String b = new String("taobao");

     

      System.out.println(a==MESSAGE); //true

      System.out.println(b==MESSAGE);  //false

     

      b = b.intern();

      System.out.println(b==MESSAGE); //true

System.out.println(a==a.intern()); //true

相關文章
相關標籤/搜索