`

【JDK优化】 Integer 自动打包机制的优化

阅读更多

我们首先来看一段代码:

Integer i=100;
Integer j=100;
System.out.println(i==j);  //true
Integer i=200;
Integer j=200;
System.out.println(i==j);  //false

 

差不多的两段代码,怎么结果完全不同呢。我们分两步来说明这个问题:

 

首先 Integer i=100; 编译器会自动将int类型常数100包装成Interger,采用的是Integer.valueOf(100); 这个方法。

 

然后我们看看valueOf(int)这个方法的源代码:

 /*
  * 返回一个表示指定的 int 值的 Integer 实例。如果不需要新的 Integer 实例,则
  * 通常应优先使用该方法,而不是构造方法 Integer(int),因为该方法有可能通过
  * 缓存经常请求的值而显著提高空间和时间性能。 
  * @param  i an <code>int</code> value.
  * @return a <tt>Integer</tt> instance representing <tt>i</tt>.
  * @since  1.5
  */
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内部类
  * 其中cache[]数组用于存放从-128到127一共256个整数
  */
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);
	}
}

 

原来如此,当-128=<i<=127的时候,返回的是IntegerCache中的数组的值;当 i>127 或 i<-128 时,返回的是Integer类对象。 这就好解释:

 

Integer i=100;
Integer j=100;
System.out.println(i==j);  //(1)

此时的 i=IntegerCache.cache[228],因此 Integer引用i中存储的是cache数组第228号元素的地址。同理j也是同一个cache数组的第228号元素的地址(因为cache是Integer的静态数组,只有一个)。i==j比较的是引用地址,因此相等。

Integer i=200;
Integer j=200;
System.out.println(i==j);  //(2)

此时的 i=new Integer(200);  同样j=new Integer(200) 。两次都在堆中开辟了Integer的对象。i 和 j 中存储的堆得对象地址是完全不同的。i==j 自然不相等了。

 

那么这样做有什么意义呢? 我们来看看API的解释:

返回一个表示指定的 int 值的 Integer 实例。如果不需要新的 Integer 实例,
则通常应优先使用该方法,而不是构造方法 Integer(int),因为该方法有可能
通过缓存经常请求的值而显著提高空间和时间性能。 

假如我们在编程时大量需要值为100的Integer对象,如果只能通过new来创建的话。是不是需要在堆中开辟大量值一样的Integer对象呢!这是相当不划算的。既然如此,Java中的字符串常量池的应用是不是可以提醒我们点什么呢?是的,IntegerCache.cache就相当于这样一个字符串常量池。 当我们需要Integer i=100的时候,直接从cache中取出第[100+128]号元素的地址赋值引用i,再次需要Integer j=100时,还是直接去这个地址赋给j ..... 是不是省去了再堆中不停的创建对象的代价了(空间,时间上的消耗都很大)。

 

 

 

 

 

分享到:
评论
38 楼 blackdog1987 2012-08-16  
blackdog1987 写道
并不是因为打包机制哦
这是JAVA的一个享元模式

String也用到了
目的在于,对于经常用的数据,进行缓存,避免平凡的创建,销毁对象所带来的开销
类似于做了一个cach

所以你可以发现
String a = "1";
String b = "1";

第一步就不说了。第二部,会去检查常量池里是否已经有这个了,如果有,就直接让B指向他,否则,创建b,然后放入常量池(所以,常量池应该是随时在更新的,我在想,既然这样,那么这个常量池可以自定义么?改JVM?这样我就可以想缓存自己喜欢的东西,当然是简单的,经常用的,非常小的,毕竟常量池是常驻内存的)
不过,并不是所有String都会这样,也是有个范围的,我记得好像也是-127  --  128
具体我忘了

如果有错大家给我指出来啊。。。我觉得应该是放常量池里了,但是不敢保证
37 楼 blackdog1987 2012-08-16  
并不是因为打包机制哦
这是JAVA的一个享元模式

String也用到了
目的在于,对于经常用的数据,进行缓存,避免平凡的创建,销毁对象所带来的开销
类似于做了一个cach
36 楼 Heart.X.Raid 2010-04-02  
laoshifu 写道
Heart.X.Raid 写道
laoshifu 写道
不过class文件是字节码来的,难道是看反编译的结果?


有IDE直接用IDE打开,MyEclipse和NetBean都可以,也可以用开发包的命令。命令我忘了,去网上查查。


NetBean打开不了,我用的是6.5的,是不是还要安装什么插件的?



MyEclipse 6.5  直接把.class文件拖进MyEclipse即可。如果是Eclipse,好像是要插件,但具体是什么我忘了。
35 楼 laoshifu 2010-04-02  
Heart.X.Raid 写道
laoshifu 写道
不过class文件是字节码来的,难道是看反编译的结果?


有IDE直接用IDE打开,MyEclipse和NetBean都可以,也可以用开发包的命令。命令我忘了,去网上查查。


NetBean打开不了,我用的是6.5的,是不是还要安装什么插件的?
34 楼 Heart.X.Raid 2010-03-18  
laoshifu 写道
不过class文件是字节码来的,难道是看反编译的结果?


有IDE直接用IDE打开,MyEclipse和NetBean都可以,也可以用开发包的命令。命令我忘了,去网上查查。
33 楼 zbird 2010-03-18  
object用==比较本来就是不对的。
==只是比较是否是同一个对象。
如果代码中存在楼主说的情况,只能说是隐藏的问题没有被发现而已。
就如用==比较string一样。
32 楼 laoshifu 2010-03-18  
不过class文件是字节码来的,难道是看反编译的结果?
31 楼 laoshifu 2010-03-18  
Heart.X.Raid 写道
shensen_wu 写道
	public static void main(String[] args) {
		
         Integer a=200;
         Integer b=200;
         int c=200;
         System.out.println("a==c  "+(a==c));
         System.out.println("b==c  "+(b==c));
         System.out.println("a==b  "+(a==b));
	}


请问LZ,对于这段代码的运行结果是:
a==c  true
b==c  true
a==b  false


既然a和b都是通过Integer新建的对象,而c是在栈中的常量,为什么a、b会和c的地址相等,而a和b的地址又不相等呢?



我绝对大家要学会看.class文件

Integer a=200;
int c=200;
System.out.println("a==c");

被编译器这样编译:

Integer a=Integer.valueOf(200); 这个时候引用a存储的是堆栈中Integer对象的地址 (注意a是对象引用)
int c=200; 编译器把常量200写进常量池,然后栈中的变量c里面存放的就是这个值200 (注意c是变量名)
int temp=a.intValue();  大家可以去看看intValue()的源代码,实际上编译器并没有temp这样的变量,而直接把intValue()的返回结构压入操作数栈。我这些写实为了大家明白过程(不存在temp的).

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

大致的过程就是这样
总结一下:
(1) 当把一个整形常量或变量直接赋值给Integer引用时,比如 Integer a=200, 或 int b=200; Integer a=b;
    这是编译器会调用Integer.valueOf()方法来自动打包int基本类型。
(2) 当比较一个Integer对象和一个int基本类型变量的时候,编译器会调用Integer对象.intValue()将Integer对象自动拆包,然后比较两个整形变量。

注意编译器给我们做了很多工作,大家不要忘记了他的辛苦。要把它的汗水记得明明白白它的心!



楼主啊,我也想研究class,但是能说一下用什么编辑器打开class文件么?一般的文本编辑器打开都是乱码。难道是看反编译的结果?
30 楼 shensen_wu 2010-03-16  
谢谢LZ!
29 楼 Heart.X.Raid 2010-03-16  
shensen_wu 写道
	public static void main(String[] args) {
		
         Integer a=200;
         Integer b=200;
         int c=200;
         System.out.println("a==c  "+(a==c));
         System.out.println("b==c  "+(b==c));
         System.out.println("a==b  "+(a==b));
	}


请问LZ,对于这段代码的运行结果是:
a==c  true
b==c  true
a==b  false


既然a和b都是通过Integer新建的对象,而c是在栈中的常量,为什么a、b会和c的地址相等,而a和b的地址又不相等呢?



我绝对大家要学会看.class文件

Integer a=200;
int c=200;
System.out.println("a==c");

被编译器这样编译:

Integer a=Integer.valueOf(200); 这个时候引用a存储的是堆栈中Integer对象的地址 (注意a是对象引用)
int c=200; 编译器把常量200写进常量池,然后栈中的变量c里面存放的就是这个值200 (注意c是变量名)
int temp=a.intValue();  大家可以去看看intValue()的源代码,实际上编译器并没有temp这样的变量,而直接把intValue()的返回结构压入操作数栈。我这些写实为了大家明白过程(不存在temp的).

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

大致的过程就是这样
总结一下:
(1) 当把一个整形常量或变量直接赋值给Integer引用时,比如 Integer a=200, 或 int b=200; Integer a=b;
    这是编译器会调用Integer.valueOf()方法来自动打包int基本类型。
(2) 当比较一个Integer对象和一个int基本类型变量的时候,编译器会调用Integer对象.intValue()将Integer对象自动拆包,然后比较两个整形变量。

注意编译器给我们做了很多工作,大家不要忘记了他的辛苦。要把它的汗水记得明明白白它的心!





28 楼 hillshills 2010-03-16  

  分析有二:

  一、避免用包装类将基本数据直接赋值。

  二、包装类也是对象,因此对象比较应该用equals方法。
27 楼 imasmallbird 2010-03-16  
shensen_wu 写道
	public static void main(String[] args) {
		
         Integer a=200;
         Integer b=200;
         int c=200;
         System.out.println("a==c  "+(a==c));
         System.out.println("b==c  "+(b==c));
         System.out.println("a==b  "+(a==b));
	}


请问LZ,对于这段代码的运行结果是:
a==c  true
b==c  true
a==b  false


既然a和b都是通过Integer新建的对象,而c是在栈中的常量,为什么a、b会和c的地址相等,而a和b的地址又不相等呢?

前两个比较应该是进行了自动的装箱与拆箱,
相当于比较的是a.intValue==200和b.intValue==200
所以相等
而第三个比较是比较的两个对象的地址,所以不相等
26 楼 shensen_wu 2010-03-16  
	public static void main(String[] args) {
		
         Integer a=200;
         Integer b=200;
         int c=200;
         System.out.println("a==c  "+(a==c));
         System.out.println("b==c  "+(b==c));
         System.out.println("a==b  "+(a==b));
	}


请问LZ,对于这段代码的运行结果是:
a==c  true
b==c  true
a==b  false


既然a和b都是通过Integer新建的对象,而c是在栈中的常量,为什么a、b会和c的地址相等,而a和b的地址又不相等呢?
25 楼 exe19 2010-03-15  
好吧 仔细看了下的。。
24 楼 wzju64676266 2010-03-15  
mercyblitz 写道
这个东西为了性能考虑,但是这种性能提升也有问题~

个人觉得也有问题,那些数字虽然比一些大数字出现的概率大点,但也占用了那些空间,而且这个依据没有什么说服力!如果想真正提高性能最好还是用基本数据类型
23 楼 wzju64676266 2010-03-15  
楼主很善于研究
22 楼 mercyblitz 2010-03-15  
thorlst 写道
我觉得这个缓存值主要目的是给Byte对象做优化的

byte值范围大家都知道是-128~127



Byte,Long,Short都有它自己的缓存!

不是为了Byte来的。
21 楼 thorlst 2010-03-15  
我觉得这个缓存值主要目的是给Byte对象做优化的

byte值范围大家都知道是-128~127
20 楼 浪客剑心 2010-03-15  
以前也碰到过类似的情况 确实要小心
19 楼 Heart.X.Raid 2010-03-15  
sarin 写道
也是比值和比地址的问题



Integer i=Integer.valueOf(100); 这里面没有比值的,全部比地址。Integer i,j永远是引用,i==j 永远比的是地址。


LZ探讨的不是比值和比地址的问题,而是一种小的优化策略。

相关推荐

Global site tag (gtag.js) - Google Analytics