1. 程式人生 > >徹底弄懂字符串常量池等相關問題

徹底弄懂字符串常量池等相關問題

加載 結果 包裝 == str2 ringbuf 總結 範圍 con

前言:

  在平時我們使用字符串一般就是拿來直接搞起,很少有深入的去想過這方面的知識,導致別人在考我們的時候,會問 String str = new String("123"); 這個一行代碼執行創建了幾個對象, String str1= str + new String("456");這行代碼中str1存儲在內存的哪個位置,堆or 字符串常量區(方法區)? 會把我們問的啞口無言了;哈哈哈哈,其實也不是水平問題,是我們平時可以仔細的去總結該類問題,下面就詳細的對這類問題進行總結;

一、首先把容易混淆以及被人問傻的幾個問題歸類匯總:[沒看本文答案解析,全部答對的請留言,我關註你]

問題1:

   String str1 = new
String("1"); str1.intern(); String str2 = "1"; System.out.println(str1 == str2); //結果是 false or true? String str3 = new String("2") + new String("2"); t3.intern(); String str4 = "22"; System.out.println(str3 == str4); //結果是 false or true?

問題2:

 String str1 = "aaa";
  String str2 
= "bbb"; String str3 = "aaabbb"; String str4 = str1 + str2; String str5 = "aaa" + "bbb"; System.out.println(str3 == str4); // false or true System.out.println(str3 == str4.intern()); // true or false System.out.println(str3 == str5);// true or false

問題3:

String t1 = new String("2");
String t2 
= "2"; t1.intern(); System.out.println(t1 == t2); //false or true String t3 = new String("2") + new String("2"); String t4 = "22"; t3.intern(); System.out.println(t3 == t4); //false or true

問題4:

    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.intern()函數

  intern函數的作用是將對應的符號常量進入特殊處理,在1.6以前 和 1.7以後有不同的處理;

  先看1.6:

      在1.6中,intern的處理是 先判斷字符串常量是否在字符串常量池中,如果存在直接返回該常量,如果沒有找到,則將該字符串常量加入到字符串常量區,也就是在字符串常量區建立該常量;

  在1.7中:

      在1.7中,intern的處理是 先判斷字符串常量是否在字符串常量池中,如果存在直接返回該常量,如果沒有找到,則在堆中建立該字符串常量,然後把堆區該對象的引用加入到字符串常量池中,以後別人拿到的是該字符串常量的引用,實際存在堆中;

2.常量池的分類【理解即可】

2.1 class文件常量池

在Class文件中除了有類的版本【高版本可以加載低版本】、字段、方法、接口等描述信息外,還有一項信息是常量池(Constant Pool Table)【此時沒有加載進內存,也就是在文件中】,用於存放編譯期生成的各種字面量和符號引用

下面對字面量和符號引用進行說明
字面量
字面量類似與我們平常說的常量,主要包括:

  1. 文本字符串:就是我們在代碼中能夠看到的字符串,例如String a = “aa”。其中”aa”就是字面量。
  2. 被final修飾的變量。

符號引用
主要包括以下常量:

  1. 類和接口和全限定名:例如對於String這個類,它的全限定名就是java/lang/String。
  2. 字段的名稱和描述符:所謂字段就是類或者接口中聲明的變量,包括類級別變量(static)和實例級的變量。
  3. 方法的名稱和描述符。所謂描述符就相當於方法的參數類型+返回值類型

2.2 運行時常量池

我們知道類加載器會加載對應的Class文件,而上面的class文件中的常量池,會在類加載後進入方法區中的運行時常量池【此時存在在內存中】。並且需要的註意的是,運行時常量池是全局共享的,多個類共用一個運行時常量池。並且class文件中常量池多個相同的字符串在運行時常量池只會存在一份。
註意運行時常量池存在於方法區中。

2.3 字符串常量池

  看名字我們就可以知道字符串常量池會用來存放字符串,也就是說常量池中的文本字符串會在類加載時進入字符串常量池。
那字符串常量池和運行時常量池是什麽關系呢?上面我們說常量池中的字面量會在類加載後進入運行時常量池,其中字面量中有包括文本字符串,顯然從這段文字我們可以知道字符串常量池存在於運行時常量池中。也就存在於方法區中。
不過在周誌明那本深入java虛擬機中有說到,到了JDK1.7時,字符串常量池就被移出了方法區,轉移到了裏了。
那麽我們可以推斷,到了JDK1.7以及之後的版本中,運行時常量池並沒有包含字符串常量池,運行時常量池存在於方法區中,而字符串常量池存在於中。

3.問題解析【重點】

3.1 問題1解析

  tring str1 = new String("1");
  解析:首先此行代碼創建了兩個對象,在執行前會在常量池中創建一個"1"的對象,然後執行該行代碼時new一個"1"的對象存放在堆區中;然後str1指向堆區中的對象;
str1.intern();
  解析:該行代碼首先查看"1"字符串有沒有存在在常量池中,此時存在則直接返回該常量,這裏返回後沒有引用接受他,【假如不存在的話在jdk1.6中會在常量池中建立該常量,在jdk1.7以後會在堆中建立該字符串,然後把他的引用放在常量池中】
String str2 = "1";
  解析:此時"1"已經存在在常量池中,str2指向常量池中的對象;

System.out.println(str1 == str2); //結果是 false or true?
  解析:str1指向堆區的對象,str2指向常量池中的對象,兩個引用指向的地址不同,輸入false; String str3 = new String("2") + new String("2");
  解析:此行代碼執行的底層執行過程是 首先使用StringBuffer的append方法將"2"和"2"拼接在一塊,然後調用toString方法new出“22”;所以此時的“22”字符串是創建在堆區的;
t3.intern();
  解析:此行代碼執行時字符串常量池中沒有"22",所以此時在jdk1.6中會在字符串常量池中創建"22",而在jdk1.7中會在堆中創建該字符串,然後將其引用添加到常量池中;
String str4 = "22";
  解析:此時的str4在jdk1.6中會指向方法區,而在jdk1,7中會指向堆區;
System.out.println(str3 == str4); //結果是 false or true?
  解析:很明顯了 jdk1.6中為false 在jdk1.7中為true;

3.2 問題2解析

 String str1 = "aaa";
 解析:str1指向方法區;
String str2 = "bbb";
 解析: str2 指向方法區
String str3 = "aaabbb";
解析:str3指向方法區
String str4 = str1 + str2;
解析:此行代碼上邊已經說過原理。str4指向堆區
String str5 = "aaa" + "bbb";
解析:該行代碼重點說明一下,jvm對其有優化處理,也就是在編譯階段就會將這兩個字符串常量進行拼接,也就是"aaabbb";所以他是在方法區中的;’
System.out.println(str3 == str4); // false or true
 解析:很明顯 為false, 一個指向堆 一個指向方法區
System.out.println(str3 == str4.intern()); // true or false
解析:jdk1.6中str4.intern會把“aaabbb”放在方法區,1.7後放在堆區,所以在1.6中會是true 但是在1.7中是false
System.out.println(str3 == str5);// true or false
解析:都指向字符串常量區,字符串長常量區在方法區,相同的字符串只存在一份,其實這個地方在擴展一下,因為方法區的字符串常量是共享的,在兩個線程同時共享這個字符串時,如果一個線程改變他會是怎麽樣的呢,其實這種場景下是線程安全的,jvm會將改變後的字符串常量在
  字符串常量池中重新創建一個處理,可以保證線程安全

3.3 問題3解析

tring t1 = new String("2");
解析:創建了兩個對象,t1指向堆區
String t2 = "2";
解析:t2指向字符串常量池
t1.intern();
解析:字符串常量池已經存在該字符串,直接返回;
System.out.println(t1 == t2); //false or true 解析:很明顯 false
String t3 = new String("2") + new String("2");
解析:過程同問題1 t3指向堆區
String t4 = "22";
解析:t4 在1.6 和 1.7中指向不同 t3.intern();
解析: 字符串常量池中已經存在該字符串 直接返回
System.out.println(t3 == t4); //false or true
解析: 很明顯為 false 指向不同的內存區

3.4 問題4解析

這個地方存在一個知識點。可能是個盲區,這次要徹底記住“

(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

 

徹底弄懂字符串常量池等相關問題