常量池(constant_pool)

来源:互联网 发布:手机淘宝的试用在哪里 编辑:程序博客网 时间:2024/06/05 00:25

常量池(constant_pool)指的是在编译期被确定,并被保存在已编译的.class文件中的一些数据。它包括了关于类、方法、接口等中的常量,也包括字符串常量和符号引用。运行时常量池是方法区的一部分。 

Java中八种基本类型的包装类的大部分都实现了常量池技术,它们是Byte、Short、Integer、Long、Character、Boolean,另外两种浮点数类型的包装类(Float、Double)则没有实现。另外Byte,Short,Integer,Long,Character这5种整型的包装类也只是在对应值在-128到127时才可使用对象池。

看下面代码:

public static void main(String[] args) throws Exception {          Integer a = 127;          Integer b = 127;          Integer c = 128;          Integer d = 128;          System.out.println(a == b);// 输出true          System.out.println(c == d);// 输出false      }  

Integer a = 127;对应的指令为:

0:   bipush  127 

2:   invokestatic   #2; //Methodjava/lang/Integer.valueOf:(I)Ljava/lang/Integer; 

Integer c = 128;对应的指令为:

12:  sipush  128 

15:  invokestatic    #2; //Methodjava/lang/Integer.valueOf:(I)Ljava/lang/Integer; 

bipush指令意思是将单字节的常量值(-128~127)推送到栈顶

sipush指令意思是将短型的常量值(-32768~32767)推送到栈顶

invokestatic指令意思是调用静态方法,这里调用的是常量池中#2指向的方法java/lang/Integer.valueOf,查看Integer.valueOf方法:


public static Integer valueOf(int i) {      final int offset = 128;      if (i >= -128 && i <= 127) { // must cache          return IntegerCache.cache[i + offset];      }      return new Integer(i);  }

IntegerCache代码:

private static class IntegerCache {  private IntegerCache(){}    static final Integer cache[] = new Integer[-(-128) + 127 + 1];    static {      for(int i = 0; i < cache.length; i++)      cache[i] = new Integer(i - 128);  }     }  


其它封装类如下:


//Boolean类也实现了常量池技术    Boolean bool1=true;    Boolean bool2=true;    System.out.println(bool1==bool2); //输出true    //浮点类型的包装类没有实现常量池技术    Double d1=1.0;    Double d2=1.0;    System.out.println(d1==d2); //输出false   

JVM会自动维护六种基本类型的常量池,int常量池中初始化-128~127的范围,所以当Integer i=127时,自动装箱过程中(Integera=Integer.valueOf(127);)是取常量池中的值,而Integer i=128时,128不在常量池范围内,所以在自动装箱过程中需要new 128,所以地址不一样.

例题

1、String s =  new  String( "xyz" );  在运行时涉及几个String实例?

  两个,一个是字符串字面量"xyz"所对应的、驻留(intern)在一个全局共享的字符串常量池中的实例,另一个是通过newString(String)创建并初始化的、内容与"xyz"相同的实例。

 String中的final用法和理解

 final只对引用的"值"(即内存地址)有效,它迫使引用只能指向初始指向的那个对象,改变它的指向会导致编译期错误。至于它所指向的对象的变化,final是不负责的。

final StringBuffer a =new StringBuffer("111"); 

final StringBuffer b =new StringBuffer("222"); 

a=b;//此句编译不通过    

final StringBuffer a =new StringBuffer("111"); 

a.append("222");//编译通过

 

String a = "a1"

String b = "a" + 1; 

System.out.println((a == b)); //result = true 

String a = "atrue"

String b = "a" + "true"

System.out.println((a == b)); //result = true 

String a = "a3.4"

String b = "a" + 3.4; 

System.out.println((a == b)); //result = true

JVM对于字符串常量的"+"号连接,将程序编译期,JVM就将常量字符串的"+"连接优化为连接后的值,拿"a" + 1来说,经编译器优化后在class中就已经是a1。在编译期其字符串常量的值就确定下来,故上面程序最终的结果都为true。

String a = "ab"

String bb = "b"

String b = "a" + bb; 

System.out.println((a == b)); //result = false

JVM对于字符串引用,由于在字符串的"+"连接中,有字符串引用存在,而引用的值在程序编译期是无法确定的,即"a" + bb无法被编译器优化,只有在程序运行期来动态分配并将连接后的新地址赋给b。所以上面程序的结果也就为false。

String a = "ab"

final String bb ="b"

String b = "a" + bb; 

System.out.println((a== b)); //result = true 

和上面唯一不同的是bb字符串加了final修饰,对于final修饰的变量,它在编译时被解析为常量值的一个本地拷贝存储到自己的常量池中或嵌入到它的字节码流中。所以此时的"a" + bb和"a" + "b"效果是一样的。故上面程序的结果为true。

String a = "ab"

final String bb =getBB(); 

String b = "a" + bb; 

System.out.println((a == b)); //result = false 

 

private static String getBB(){ 

    return "b"

}

JVM对于字符串引用bb,它的值在编译期无法确定,只有在程序运行期调用方法后,将方法的返回值和"a"来动态连接并分配地址为b,故上面程序的结果为false。

结论:通过上面4个例子可以得出得知:

String s  =  "a" + "b" +"c";   

就等价于Strings = "abc";  int i = 1+2+3;也等价于inti = 6;

String  a =  "a";   

String  b =  "b";   

String  c =  "c";   

String  s =   a  + b  +  c;    

这个就不一样了,最终结果等于:

StringBuffertemp = new StringBuffer();   

temp.append(a).append(b).append(c);   

Strings = temp.toString();  

由上面的分析结果,可就不难推断出String 采用连接运算符(+)效率低下原因分析,形如这样的代码:

publicclass Test { 

    public static void main(String args[]){ 

        String s = null; 

        for(int i = 0; i < 100; i++) { 

            s += "a"; 

        } 

    } 

}  

每做一次+ 就产生个StringBuilder对象,然后append后就扔掉。下次循环再到达时重新产生个StringBuilder对象,然后 append字符串,如此循环直至结束。 如果我们直接采用 StringBuilder 对象进行 append的话,我们可以节省 N - 1 次创建和销毁对象的时间。所以对于在循环中要进行字符串连接的应用,一般都是用StringBuffer或StringBulider对象来进行 append操作。

String.intern()解析:

Java语言并不要求常量一定只能在编译期产生,运行时也可能将新的常量放入常量池中,这种特性用的最多的就是String.intern()方法。

String的intern()方法就是扩充常量池的一个方法;当一个String实例str调用intern()方法时,Java查找常量池中是否有相同Unicode的字符串常量,如果有,则返回其的引用,如果没有,则在常量池中增加 一个Unicode等于str的字符串并返回它的引用。

String s0= "xyz"

String s1=new String("xyz"); 

String s2=new String("xyz");   

System.out.println(s0==s1); 

s1.intern(); 

s2=s2.intern(); //把常量池中“pku”的引用赋给s2 

System.out.println( s0==s1); 

System.out.println( s0==s1.intern() ); 

System.out.println( s0==s2 ); 

输出为:

false

false //虽然执行了s1.intern(),但它的返回值没有赋给s1

true //说明s1.intern()返回的是常量池中”pku”的引用

true

 

String s1=new String("xyz"); 

String s2=s1.intern(); 

System.out.println( s1==s1.intern() ); 

System.out.println( s1+" "+s2 ); 

System.out.println( s2==s1.intern() );

输出为:

false

xyz xyz

true
原创粉丝点击