Java常量池

  1. 基础问题:什么时候用int 什么时候用Integer?
    (1)Integer是int的包装类;int是基本数据类型;
    (2)Integer的默认值是null;int的默认值是0。

  2. 第二个问题,为什么Integer对象的范围是-128~127?

在Integer类中有一个静态内部类IntegerCache,在IntegerCache类中有一个Integer数组,
用以缓存当数值范围为-128~127时的Integer对象

java内部为了节省内存,IntegerCache类中有一个数组缓存了值从-128到127的Integer对象。
当我们调用Integer.valueOf(int i)的时候,如果i的值时结余-128到127之间的,
会直接从这个缓存中返回一个对象,否则就new一个新的Integer对象。

所有整数类型的类都有类似的缓存机制:

有 ByteCache 用于缓存 Byte 对象
ShortCache 用于缓存 Short 对象
LongCache 用于缓存 Long 对象
Byte,Short,Long 的缓存池范围默认都是: -128 到 127。
可以看出,Byte的所有值都在缓存区中,用它生成的相同值对象都是相等的。

所有整型(Byte,Short,Long)的比较规律与Integer是一样的。 同时Character对象也有CharacterCache
缓存 池,范围是 0 到 127。

注意Integer的缓存数据是不会被垃圾回收。


  1. 创建Interger 的几种方式

问题:当你需要产生一个整形的包装类的实例的时候(比如整数10),有两种方式:

第一种,使用构造函数new 一个对象:

Integer i=new Integer(10);

第二种,使用静态工厂方法产生实例(我会告诉你其实java里所有的ValueOf都是静态工厂方法吗?):

Integer i=Integer.ValueOf(10);
第三种 是自动装箱的Interger a=1

  1. 对于上面三种方式,你选哪种?哪个比较好?

我选第二种,为什么?因为,当你用第一种方式时每次都会产生一个新的实例,而当你使用静态工厂方法时,不一定会产生一个新的实例,不超过常量池中的都不会产生实例

至于第二种和第三种是无非是自动装箱和手动装箱,要选哪个我还真的不知道?

	    Integer a =new Integer(1);
        Integer b =1;
        Integer c =Integer.valueOf(1);
        结果:
        b==c
        但是a!= b  a!=c
  1. 自动装箱引起的性能问题?

     如果有人告诉你:“只要修改一个字符,下面这段代码的运行速度就能提高5倍。”,你觉得可能么?
     long t = System.currentTimeMillis();
     Long sum = 0L;
     for (long i = 0; i < Integer.MAX_VALUE; i++) {
         sum += i;
     }
     System.out.println("total:" + sum);
     System.out.println("processing time: " + (System.currentTimeMillis() - t) + " ms");
     输出结果:
     total:2305843005992468481 processing time: 63556 ms
     
     将Long修改为long,再来看一下运行结果
     long t = System.currentTimeMillis();
     long sum = 0L;
     for (long i = 0; i < Integer.MAX_VALUE; i++) {
         sum += i;
     }
     System.out.println("total:" + sum);
     System.out.println("processing time: " + (System.currentTimeMillis() - t) + " ms");
     输出结果:
     total:2305843005992468481 processing time: 12229 ms
     
     事实证明,仅仅修改了一个字符,性能提高了不止一倍两倍。
     那,就究竟是什么原因导致的呢? 因为,+这个操作符不适用Integer对象
     ,在进行数值相加操作之前会发生自动拆箱操作,转换成int,相加之后还会发生自动拆箱操作,装换成Integer对象。
     其内部变化如下:
     sum = sum.longValue() + i;
     Long sum = new Long(sum);
     很明显,在上面的循环中会创建2147483647个”Long“类型实例,在这样庞大的循环中,
     会降低程序的性能并且加重了垃圾回收的工作量生成无用对象增加GC压力
    

因为自动装箱会隐式地创建对象,像前面提到的那样,如果在一个循环体中,
会创建无用的中间对象,这样会增加GC压力,拉低程序的性能。
所以在写循环时一定要注意代码,避免引入不必要的自动装箱操作




**常量池:**
 常量池:JVM为每个已加载的类型维护一个常量池,常量池就是这个类型用到的常量的一个有序集合。
 包括:
 		1 直接常量
 		2 基本类型
		  	3  String
 		4 对其他类型、方法、字段的符号引用(什么是符号引用文章最后说)。
池中的数据和数组一样通过索引访问。由于常量池包含了一个类型所有的对其他类型、方法、字段的符号引用,
所以常量池在Java的动态链接中起了核心作用。常量池存在于堆中。



<hr>


**基本类型和基本类型的包装类**

  **基本类型有**:byte、short、char、int、long、boolean。

  **基本类型的包装类分别是**:Byte、Short、Character、Integer、Long、Boolean。

  注意区分大小写。

  二者的区别是:基本类型体现在程序中是普通变量,基本类型的包装类是类,体现在程序中是引用变量。

  因此二者在内存中的存储位置不同:基本类型存储在栈中,而基本类型包装类存储在堆中。上边提到的这些包装类都实现了常量池技术,另外**两种浮点数类型的包装类则没有实现**。另外,**String类型也实现了常量池技术**。

测试常量池

public class test {
    public static void main(String[] args) {    
        objPoolTest();
    }

    public static void objPoolTest() {
        int i = 40;
        int i0 = 40;
        Integer i1 = 40;
        Integer i2 = 40;
        Integer i3 = 0;
        Integer i4 = new Integer(40);
        Integer i5 = new Integer(40);
        Integer i6 = new Integer(0);
        Double d1=1.0;
        Double d2=1.0;
        
        System.out.println("i=i0\t" + (i == i0));
        System.out.println("i1=i2\t" + (i1 == i2));
        System.out.println("i1=i2+i3\t" + (i1 == i2 + i3));
        System.out.println("i4=i5\t" + (i4 == i5));
        System.out.println("i4=i5+i6\t" + (i4 == i5 + i6));    
        System.out.println("d1=d2\t" + (d1==d2)); 
        
        System.out.println();        
    }

}



输出结果

i=i0       true
i1=i2      true
i1=i2+i3   true
i4=i5      false
i4=i5+i6   true
d1=d2      false


结果分析:

  1. i和i0均是普通类型(int)的变量,所以数据直接存储在栈中,而栈有一个很重要的特性:栈中的数据可以共享。当我们定义了int i = 40;,再定义int i0 = 40;这时候会自动检查栈中是否有40这个数据,如果有,i0会直接指向i的40,不会再添加一个新的40。

  2. i1和i2均是引用类型,在栈中存储指针,因为Integer是包装类。由于Integer 包装类实现了常量池技术,因此i1、i2的40均是从常量池中获取的,均指向同一个地址,因此i1=12。

  3. 很明显这是一个加法运算,Java的数学运算都是在栈中进行的,Java会自动对i1、i2进行拆箱操作转化成整型,因此i1在数值上等于i2+i3。

  4. 4.i4和i5 均是引用类型,在栈中存储指针,因为Integer是包装类。但是由于他们各自都是new出来的,因此不再从常量池寻找数据,而是从堆中各自new一个对象,然后各自保存指向对象的指针,所以i4和i5不相等,因为他们所存指针不同,所指向对象不同。

  5. 这也是一个加法运算,和3同理。说明基本类型包装类的计算也是在常量池栈中进行的

  6. 6.d1和d2均是引用类型,在栈中存储指针,因为Double是包装类。但Double包装类没有实现常量池技术,因此Doubled1=1.0;相当于Double d1=new Double(1.0);,是从堆new一个对象,d2同理。因此d1和d2存放的指针不同,指向的对象不同,所以不相等。


小结:
  1. 以上提到的几种基本类型包装类均实现了常量池技术,但他们**维护的常量仅仅是【-128至127】这个范围内的常量,如果常量值超过这个范围,就会从堆中创建对象,不再从常量池中取。**比如,把上边例子改成Integer i1 = 400; Integer i2 = 400;,很明显超过了127,无法从常量池获取常量,就要从堆中new新的Integer对象,这时i1和i2就不相等了。

  2. String类型也实现了常量池技术,但是稍微有点不同。String型是先检测常量池中有没有对应字符串,如果有,则取出来;如果没有,则把当前的添加进去。

  3. 上面也说明了一个问题,直接用Interger i1 = 23 ;这样的是在常量池中,但是Integer i2 =new Integer(23) ;这样的不再常量池中。

  4. 而且说明了不但int Integer 的计算是在栈中进行的,而且Integer i3 =new Interger (33);这样的也是在栈中进行的

脚注:

(1) 符号引用,顾名思义,就是一个符号,符号引用被使用的时候,才会解析这个符号。如果熟悉linux或unix系统的,可以把这个符号引用看作一个文件的软链接,当使用这个软连接的时候,才会真正解析它,展开它找到实际的文件对于符号引用,在类加载层面上讨论比较多,源码级别只是一个形式上的讨论。

当一个类被加载时,该类所用到的别的类的符号引用都会保存在常量池,实际代码执行的时候,首次遇到某个别的类时,JVM会对常量池的该类的符号引用展开,转为直接引用,这样下次再遇到同样的类型时,JVM就不再解析,而直接使用这个已经被解析过的直接引用。

除了上述的类加载过程的符号引用说法,对于源码级别来说,就是依照引用的解析过程来区别代码中某些数据属于符号引用还是直接引用,如,System.out.println(“test” +“abc”);//这里发生的效果相当于直接引用,而假设某个String s = “abc”; System.out.println(“test” + s);//这里的发生的效果相当于符号引用,即把s展开解析,也就相当于s是"abc"的一个符号链接,也就是说在编译的时候,class文件并没有直接展看s,而把这个s看作一个符号,在实际的代码执行时,才会展开这个。


常量池超过127之后怎么办呢?

public class StackAndHeap {



    public static void main(String[] args) {
     int i =40 ;
     int i0 =40;
        System.out.println("不超过127  int 相等吗");
        System.out.println(i ==i0);
     Integer i1 =40 ;
     Integer i2 =40 ;
        System.out.println("不超过127  Integer 相等吗");
        System.out.println(i2 ==i1);
        System.out.println("不超过127  int   Integer 相等吗");
        System.out.println(i2 ==i);

     Double d1 =1.0;
     Double d2 =1.0 ;
        System.out.println("double实现常量池了吗");
        System.out.println(d1 ==d2);

        System.out.println("===============================================");
                int ai =300 ;
                int bi =300 ;
        System.out.println("超过127之后,两个int还能判定等于吗");
        System.out.println(ai == bi);
                Integer ia = 300 ;

        System.out.println("超过127之后int 和Integer 是否相同");
        System.out.println(ia ==ai);
                Integer ib = 300 ;
        System.out.println("超过127之后 Integer 是否相同");
        System.out.println(ia ==ib);
















    }


}

答案:

不超过127  int 相等吗
true
不超过127  Integer 相等吗
true
不超过127  int   Integer 相等吗
true
double实现常量池了吗
false
===============================================
超过127之后,两个int还能判定等于吗
true
超过127之后int 和Integer 是否相同
true
超过127之后 Integer 是否相同
false

总结:

上面也就是说超过127常量池维护的范围之后,就会从堆中创建对象,不再从常量池中取


























  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值