1. 程式人生 > >java 堆,棧,字符串

java 堆,棧,字符串

及其 字符串 編譯器優化 堆內存 string類 osc 檢查 發生 buffer

public class StringDemo{ private static final String MESSAGE="taobao"; public static void main(String [] args) { String a ="tao"+"bao"; String b="tao"; String c="bao"; System.out.println(a==MESSAGE); System.out.println((b+c)==MESSAGE); } }

對於這道題,考察的是對String類型的認識以及編譯器優化。Java中String不是基本類型,但是有些時候和基本類型差不多,如String b = "tao" ; 可以對變量直接賦值,而不用 new 一個對象(當然也可以用 new)。所以String這個類型值得好好研究下。

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

int i=1;

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

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

技術分享

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

技術分享

如果j++,這時指向的變量並不會改變,而是在棧內尋找新的常量(比原來的常量大1),如果棧內存有則指向它,如果沒有就在棧內存中加入此常量並將j指向它。這種基本類型之間比較大小和我們邏輯上判斷大小是一致的。如定義i和j是都賦值1,則i==j結果為true。==用於判斷兩個變量指向的地址是否一樣。i==j就是判斷i指向的1和j指向的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?當然不等,s和w指向堆內存中不同的String對象。如果判斷兩個String對象相等呢?用equals方法。

技術分享

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

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

final String b = "tao" ;

final String c = "bao" ;

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

現在b和c不可能再次賦值了,所以編譯器將b+c編譯成了”taobao”。因此,這時的結果是true。

在字符串相加中,只要有一個是非final類型的變量,編譯器就不會優化,因為這樣的變量可能發生改變,所以編譯器不可能將這樣的變量替換成常量。例如將變量b的final去掉,結果又變成了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

java 堆,棧,字符串