天天看點

python 常量池_聊一聊讓我蒙蔽一晚上的各種常量池

在寫之前我們先來看幾個問題,假如你對這些問題已經很懂了的話,那大可不用看這篇文章,如果不大懂的話,那麼可以看看我的想法。

問題1:

public static void main(String[] args){

String t1 = new String("2");

t1.intern();

String t2 = "2";

System.out.println(t1 == t2);

String t3 = new String("2") + new String("2");

t3.intern();

String t4 = "22";

System.out.println(t3 == t4);

}

答案輸出:

JDK1.6是 false false

JDK1.7是 false true;

問題2(把問題1的語句調換一下位置)

public static void main(String[] args){

String t1 = new String("2");

String t2 = "2";

t1.intern();

System.out.println(t1 == t2);

String t3 = new String("2") + new String("2");

String t4 = "22";

t3.intern();

System.out.println(t3 == t4);

}

答案輸出:

false false

對于這兩個問題,看了幾個人的部落格,可謂百花齊放,越看越懵逼

問題3

public static void main(String[] args){

Integer a = 1;

Integer b = 2;

Integer c = 3;

Integer d = 3;

Integer e = 321;

Integer f = 321;

Long g = 3L;

System.out.println(c == d);

System.out.Println(e == f);

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

System.out.println(c.equals(a+b));

System.out.println(g == (a + b));

System.out.println(g.equals(a + b));

}

答案輸出:

true

false

true

true

true

false

問題4:

運作時常量池與字元串常量池是什麼關系?包含?

在解決問題之前,我們先來簡單了解一些常量池的一些知識點(大部分來源于周志明的深入Java虛拟機這本書)。

JVM中的幾種常量池

1.class檔案常量池

在Class檔案中除了有類的版本、字段、方法、接口等描述資訊外,還有一項資訊是常量池(Constant Pool Table),用于存放編譯期生成的各種字面量和符号引用。

這裡簡單解釋下字面量和符号引用

字面量

字面量類似與我們平常說的常量,主要包括:

文本字元串:就是我們在代碼中能夠看到的字元串,例如String a = "aa"。其中"aa"就是字面量。

被final修飾的變量。

符号引用

主要包括以下常量:

類和接口和全限定名:例如對于String這個類,它的全限定名就是java/lang/String。

字段的名稱和描述符:所謂字段就是類或者接口中聲明的變量,包括類級别變量(static)和執行個體級的變量。

方法的名稱和描述符。所謂描述符就相當于方法的參數類型+傳回值類型。

2.運作時常量池

我們上面說的class檔案中的常量池,它會在類加載後進入方法區中的運作時常量池。并且需要的注意的是,運作時常量池是全局共享的,多個類共用一個運作時常量池。并且class檔案中常量池多個相同的字元串在運作時常量池隻會存在一份。

注意運作時常量池存在于方法區中。

3.字元串常量池

看名字我們就可以知道字元串常量池會用來存放字元串,也就是說常量池中的文本字元串會在類加載時進入字元串常量池。

那字元串常量池和運作時常量池是什麼關系呢?上面我們說常量池中的字面量會在類加載後進入運作時常量池,其中字面量中有包括文本字元串,顯然從這段文字我們可以知道字元串常量池存在于運作時常量池中。也就存在于方法區中。

不過在周志明那本深入java虛拟機中有說到,到了JDK1.7時,字元串常量池就被移出了方法區,轉移到了堆裡了。

那麼我們可以推斷,到了JDK1.7以及之後的版本中,運作時常量池并沒有包含字元串常量池,運作時常量池存在于方法區中,而字元串常量池存在于堆中。

說了這麼多,現在我們開始來解決上面提出了問題。

解決問題

問題1:

public static void main(String[] args){

String t1 = new String("1");

t1.intern();

String t2 = "1";

System.out.println(t1 == t2);

String t3 = new String("2") + new String("2");

t3.intern();

String t4 = "22";

System.out.println(t3 == t4);

}

答案輸出:

JDK1.6是 false false。

JDK1.7是 false true;

在解決這個問題之前,我們先來看另外一道面試中經常會問到的問題。

String t = new String("tt");

假如程式中隻有這樣一行代碼,那麼這行代碼建立了幾個對象?

我們上面說過,"tt"屬于字面量,那麼它會在類加載之後存在于字元串常量池中,也就是說,在 String t = new String("tt")這句代碼執行之前,字元串常量池就已經建立了"tt"這個字元串對象了,我們都知道,new這個關鍵字會在堆中建立一個對象。

是以,這段代碼建立了兩個對象。一個在堆中,一個在字元串常量池中。

那麼下面這段代碼又是建立了幾個對象呢?

String t1 = new String("tt");

String t2 = new String("tt");

答是這段代碼建立了三個對象,我們上面說了,字元串常量池隻會儲存一份内容相同的字元串。也就是說,在這兩句代碼執行之前,字元串常量池就已經建立了内容為"tt"的對象了。這兩句代碼執行之後,又在堆中建立了兩個,是以一共建立了三個。

那麼下面這段代碼又是建立了幾個對象?

String t = "tt";

答是1個,在這段代碼執行之前,字元串常量池已經建立了一個"tt"的對象,但由于這行代碼并非用new的方法,是以虛拟機會在字元串常量池中尋找是否有内容為"tt"的字元串對象,如果有,則直接傳回這個字元串的引用,是以最終結果隻建立了一個對象。

回到我們的問題,在這裡我們先解釋下String 的intern方法。

例如我們調用了t.intern()。

在JDK1.6的時候,調用了這個方法之後,虛拟機會在字元串常量池在查找是否有内容與"tt"相等的對象,如果有,則傳回這個對象,如果沒有,則會在字元串常量池中添加這個對象。注意,是把這個對象添加到字元串常量池。

到了JDK1.7之後,如果調用了intern這個方法,虛拟機會在字元串常量池在查找是否有内容與"tt"相等的對象,如果有,則傳回這個對象,如果沒有。則會在堆中把這個對象的引用複制添加到字元串常量池中。注意,這個時候添加的是對象在堆中的引用。

現在開始來分析問題中的代碼

t1 = new String("1")。

這句代碼執行之前,字元串常量池中已經有"t"這個對象,執行之後會在堆中也建立一個"t"的對象,此時t1指向的是堆中的對象。

t1.intern();

這句代碼執行之後,會在字元串常量池尋早内容為"t"的對象,字元串常量池已經存在這個對象了,把這個對象傳回(不過傳回之後并沒有變量來接收)。

t2 = "1"。

這句執行後會在字元串常量池查找内容為"t"的對象,字元串常量池已經有這個對象了,傳回給t2,此時t2指向的是常量池中的對象。

一個是常量池中的對象,一個是在堆中的對象,兩者能相等嗎?是以

t1 與 t2不相等。

接着下面

t3 = new String("2") + new String("2");

這段代碼調用之前,字元串常量池有一個"2"的對象,執行之後,實際上會調用StringBuilder的append()方法類進行拼接,最後在堆中建立一個"22"的對象,注意,此時字面量并沒有"22"這個字元串,也就是說在字元串常量池并沒有"22"這個對象。此時t3指向堆中"22"這個對象

t3.intern();

執行這個方法之後

在JDK1.6的時候,它在字元串常量池中并沒有找到内容為"22"的對象,是以這個時候會把這個對象添加到字元串常量池,并把這個對象傳回(此時并沒有變量來接收這個傳回的對象)。注意添加的是對象,而并非引用。

t4 = "22"。

這句代碼執行後,會傳回字元串常量池中内容為"22"對象,此時t4指向的是字元串常量池中的對象。

顯然,一個對象在字元串常量池,一個在堆中,兩個對象并非是同一個對象,是以在JDK1.6的時候,t3與t4不相等。

但是在JDK1.7的時候

t3.intern()執行之後,由于在字元串常量池在并沒有内容為"22"的對象,是以會把堆中該對象的引用指派到字元串常量池。注意此時字元串常量池儲存的是堆中這個對象的引用。

t4 = "22"。

執行這句代碼之後,從字元串常量池傳回給t4的是堆中對象的引用。此時t4指向的實際上是堆中對象的引用,也就是說,t3和t4指向的是同一個對象。

是以t3與t4相等。

不知道你明白了沒有?反正我是搞了好久才明白...

問題2

至于問題2,我就隻講下半部分的代碼,上半部分如果你看懂了問題1,那麼問題2也差不多自然懂了。

String t3 = new String("2") + new String("2");

String t4 = "22";

t3.intern();

System.out.println(t3 == t4);

t3 = new String("2") + new String("2")。

這段代碼調用之前,字元串常量池有一個"2"的對象,執行之後,實際上會調用StringBuilder的append()方法類進行拼接,最後在堆中建立一個"22"的對象。此時t3指向堆中"22"這個對象

t4 = "22"。

這句代碼執行之前,字元串常量池已經存在"22"這個對象了,所有直接把這個對象傳回給t4,此時t4指向的是字元串常量池中的對象.

是以t3和t4肯定不是同一個對象啊,t3.intern這句幾乎可以忽略,不會給t3和t4造成任何影響。

問題3

public static void main(String[] args){

Integer a = 1;

Integer b = 2;

Integer c = 3;

Integer d = 3;

Integer e = 321;

Integer f = 321;

Long g = 3L;

System.out.println(c == d);

System.out.Println(e == f);

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

System.out.println(c.equals(a+b));

System.out.println(g == (a + b));

System.out.println(g.equals(a + b));

}

對于這個問題,我簡單說一下可能你就懂了。

(1). 記憶體中有一個java基本類型封裝類的常量池。這些類包括

Byte, Short, Integer, Long, Character, Boolean。需要注意的是,Float和Double這兩個類并沒有對應的常量池。

(2).上面5種整型的包裝類也隻是在對象數值在-128~127才可以使用這些常量池。

(3). 在周志明的那本虛拟機中有這樣一句話:包裝類的

"=="運作符在不遇到算術運算的情況下不會自動拆箱,以及他們的equals()方法不處理資料類型的關系,可以推斷出如果遇到“==”兩邊有算術運算是話就會自動拆箱和進行資料類型轉換處理。

(4).Long的equals方法會先判斷是否是Long類型。

(5).無論是Integer還是Long,他們的equals方法比較的是數值。

是以:

System.out.println(c == d)。

由于常量池的作用,c與d指向的是同一個對象(注意此時的==比較的是對象,也就是位址,而不是數值)。是以為true

System.out.println(e == f)。

由于321超過了127,是以常量池失去了作用,是以e和f數值雖然相同,但不是同一個對象,以此為false。

System.out.println(c == (a+b))。

此時==兩邊有算術運算,會進行拆箱,是以此時比較的是數值,而并非對象。是以為true。

System.out.println(c.equals(a+b))

c與a+b的數值相等,為true。

System.out.pirnln(g == (a + b))

由于==兩邊有算術運算,是以比較的是數值,是以為true。

System.out.println(g.equals(a+b))。

Long類型的equal在比較是時候,會先判斷a+b是否為Long類型,顯然a+b不是,是以false

問題到此就結束了,以上便是自己的了解,以上如果有不對勁的地方,非常歡迎你的指點。

完。

關注公衆号「苦逼的碼農」,擷取更多原創文章,背景回複「禮包」送你一份特别的大禮包