Java的Integer常量池和String常量池


1.Integer的常量池

看下面一段代碼:

package cn.qlq.test;

public class ArrayTest {
    public static void main(String[] args) {
        Integer i1 = new Integer(1);
        Integer i2 = new Integer(1);
        System.out.println(i1.hashCode());
        System.out.println(i2.hashCode());
        System.out.println(i1 == i2);
        System.out.println(i1.equals(i2));

        System.out.println("-------------------");

        Integer i3 = 1;
        Integer i4 = 1;
        System.out.println(i3.hashCode());
        System.out.println(i4.hashCode());
        System.out.println(i3 == i4);
        System.out.println(i3.equals(i4));

    }

}

 

 

 1
1
false
true
-------------------
1
1
true
true

 

基本知識:我們知道,如果兩個引用指向同一個對象,用==表示它們是相等的。如果兩個引用指向不同的對象,用==表示它們是不相等的,即使它們的內容相同。

解釋:Integer i1 = new Integer(1)的時候是在Java堆中創建一個Integer對象,i1指向堆中的對象,i1與常量池沒關系,所以i1==i2為false。

  Integer i3=1;的時候是從常量池中查找值為1的常量,i3指向該常量;Integer i4=1的時候會直接指向該常量,所以 i3 == i4為true。

 

這就是它有趣的地方了。如果你看去看 Integer.Java 類,你會發現有一個內部私有類,IntegerCache.java,它緩存了從-128到127之間的所有的整數對象。

 

 

 

所以事情就成了,所有的小整數在內部緩存,然后當我們聲明類似——

        Integer bInteger=127;

 

 

它實際在內部的操作是:

        Integer bInteger=Integer.valueOf(127);

 

 

 

現在,如果我們去看valueOf()方法,我們可以看到:

    public static Integer valueOf(int i) {
        assert IntegerCache.high >= 127;
        if (i >= IntegerCache.low && i <= IntegerCache.high)
            return IntegerCache.cache[i + (-IntegerCache.low)];
        return new Integer(i);
    }

如果值的范圍在-128到127之間,它就從高速緩存返回實例。

所以…下面這兩個指向同一個對象:

        Integer aInteger=127;
        Integer bInteger=127;

我們可以得到true。


現在你可能會問,為什么這里需要緩存?

  合乎邏輯的理由是,在此范圍內的“小”整數使用率比大整數要高,因此,使用相同的底層對象是有價值的,可以減少潛在的內存占用。

  然而,通過反射API你會誤用此功能。

 

 

 

現在對代碼進行反編譯和反匯編查看:

package zd.dms.test;

public class ArrayTest {

    public static void main(String[] args) {
        Integer i1 = 25;
        Integer i2 = new Integer(26);
    }
}

 

反編譯:

package zd.dms.test;

public class ArrayTest
{
  public static void main(String[] paramArrayOfString)
  {
    Integer localInteger1 = Integer.valueOf(25);
    Integer localInteger2 = new Integer(26);
  }
}

 

反匯編:

C:\Users\Administrator\Desktop>javap -c -v ArrayTest.class
Classfile /C:/Users/Administrator/Desktop/ArrayTest.class
  Last modified 2018-9-3; size 384 bytes
  MD5 checksum 6535da703ea8fa15da765de7bb03300b
  Compiled from "ArrayTest.java"
public class zd.dms.test.ArrayTest
  SourceFile: "ArrayTest.java"
  minor version: 0
  major version: 51
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #6.#15         //  java/lang/Object."<init>":()V
   #2 = Methodref          #3.#16         //  java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
   #3 = Class              #17            //  java/lang/Integer
   #4 = Methodref          #3.#18         //  java/lang/Integer."<init>":(I)V
   #5 = Class              #19            //  zd/dms/test/ArrayTest
   #6 = Class              #20            //  java/lang/Object
   #7 = Utf8               <init>
   #8 = Utf8               ()V
   #9 = Utf8               Code
  #10 = Utf8               LineNumberTable
  #11 = Utf8               main
  #12 = Utf8               ([Ljava/lang/String;)V
  #13 = Utf8               SourceFile
  #14 = Utf8               ArrayTest.java
  #15 = NameAndType        #7:#8          //  "<init>":()V
  #16 = NameAndType        #21:#22        //  valueOf:(I)Ljava/lang/Integer;
  #17 = Utf8               java/lang/Integer
  #18 = NameAndType        #7:#23         //  "<init>":(I)V
  #19 = Utf8               zd/dms/test/ArrayTest
  #20 = Utf8               java/lang/Object
  #21 = Utf8               valueOf
  #22 = Utf8               (I)Ljava/lang/Integer;
  #23 = Utf8               (I)V
{
  public zd.dms.test.ArrayTest();
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: return
      LineNumberTable:
        line 3: 0

  public static void main(java.lang.String[]);
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=3, locals=3, args_size=1
         0: bipush        25
         2: invokestatic  #2                  // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
         5: astore_1
         6: new           #3                  // class java/lang/Integer
         9: dup
        10: bipush        26
        12: invokespecial #4                  // Method java/lang/Integer."<init>":(I)V
        15: astore_2
        16: return
      LineNumberTable:
        line 6: 0
        line 7: 6
        line 8: 16
}

 

bipush 25     將25推至棧頂

invokestatic  調用Integer的靜態方法valueOf(int)方法

astore_1          將棧頂引用型數值存入第二個本地變量

new   調用new Integer(int)

 dup   復制棧頂數值(數值不能是long或double類型的)並將復制值壓入棧頂

bipush 26     將26推至棧頂

invokespecial  調用Integer的初始化方法(init)

astore_2         將棧頂引用型數值存入第三個本地變量

return   返回,類型是void

 

補充:

  aload_0                                                            //將this引用推送至棧頂,即壓入棧。

 

總結:Integer i = value;如果i是在-128到127之間,不會去堆中創建對象,而是直接返回IntegerCache中的值;如果值不在上面范圍內則會從堆中創建對象。= 走的是valueOf()方法,valueOf(int)會走緩存。

  Integer i2 = new Integer(xxxx);不管參數的value是多少都會從堆中創建對象,與IntegerCache沒關系。

  

 

 

2.String常量池問題:

package cn.qlq.test;

public class ArrayTest {
    public static void main(String[] args) {
        String s1 = new String("1");
        String s2 = new String("1");
        System.out.println(s1.hashCode());// 49
        System.out.println(s2.hashCode());// 49
        System.out.println(s1 == s2);// false
        System.out.println(s1.equals(s2));// true

        System.out.println("-------------------");

        String s3 = "1";
        String s4 = "1";
        System.out.println(s3 == s4);// true
        System.out.println(s3.equals(s4));// true
        System.out.println(s3.hashCode());// 49
        System.out.println(s4.hashCode());// 49

    }

}

 

  String的hashCode不是返回地址,是對其值進行遍歷運算。與地址沒關系,只對值計算,所以所有的hashCode一樣。

  String s1 = new String("1"); 是在堆中創建一個String對象,並檢查常量池中是否有字面量為"1"的常量,沒有的話在常量區創建"1"並將堆中的對象指向該常量,有的話堆中的對象直接指向"1";

  String s2 = new String("1"); 又在堆中創建一個String對象,並將s2指向該對象,其字面量"1"在前面已經創建,所以不會再創建常量區中創建字符串;

  

  String s3 = "1";   檢查常量池中有沒有字面量為"1"的字符串,如果沒有則創建並將s3指向該常量;有的話直接指向該該常量;

  String s4 = "1"  的時候常量池已經有1,所以不會再創建對象,也就是s3與s4指向同一個對象。

 

所以我們可以用下面圖解解釋,String s = new String("xxx")在檢查常量池的時候會涉及到堆中創建對象;String s = "x"直接檢查常量池,不會涉及堆。

 

 

如下圖解:

 

 

一道經典的面試題:new String("abc")創建幾個對象?

  簡單的回答是一個或者兩個,如果是常量區有值為"abc"的值,則只在堆中創建一個對象;如果常量區沒有則會在常量區創建"abc",此處的常量區是方法區的運行時常量池(也稱為動態常量區)。

 

 

   我們需要明白只要是new都會在堆中創建對象。直接String s = "xxx"不會涉及堆,只在常量區檢查是否有該常量。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM