引言
闲来无事,随意点了项目中代码的一些源码,想看看这些代码中依赖的源码中都怎么实现的,机缘巧合进入Integer的源码。复习之后记录一下自己的心得。
例
闲话少说,代码敬上
private static void demo4(){
int a = 128, b = 128;
System.out.println("run result NO.1->"+(a == b));
Integer c = 128, d = 128;
System.out.println("run result NO.2->"+(c == d));
Integer e = 100, f = 100;
System.out.println("run result NO.3->"+(e == f));
}
代码执行结果:
run result NO.1->true
run result NO.2->false
run result NO.3->true
从执行上可以看出,变量c、d都是128而执行结果是false。这里就涉及到自动拆装箱机制。
分析
自动拆装箱
百度百科中关于自动拆装箱的解释:
Java拆装箱就是Java相应的基本数据类型和引用类型的互相转化
如 Integer a = 1;
其中 a
为Integer
类型,而1
为int
类型,且Integer
与int
之间并没有继承关系,按照java一般情况处理运行程序会报异常。但是因为自动拆装箱的存在,在为Integer
类型的变量赋int
类型的值时,Java会自动将int
类型的转换为Integer
类型。
即 Integer a = Integer.valueOf(1);
valueOf()
在Java api中解释为:
//返回一个 Integer指定的 int值的 Integer实例。
static Integer valueOf(int i)
//返回一个 Integer对象,保存指定的值为 String 。
static Integer valueOf(String s)
//返回一个 Integer对象,保存从指定的String中 String的值,当用第二个参数给出的基数进行解析时。
static Integer valueOf(String s, int radix)
为什么执行==后结果不相同?
看了一些概念后我我们回归问题的本身。我们先增加一下辅助信息来分析问题:
private static void demo4(){
int a = 128, b = 128;
System.out.println("run result NO.1->"+(a == b));
System.out.println("a->identityHashCode="+System.identityHashCode(a));
System.out.println("b->identityHashCode="+System.identityHashCode(b));
Integer c = 128, d = 128;
System.out.println("run result NO.2->"+(c == d));
System.out.println("c->identityHashCode="+System.identityHashCode(c));
System.out.println("d->identityHashCode="+System.identityHashCode(d));
Integer e = 100, f = 100;
System.out.println("run result NO.3->"+(e == f));
System.out.println("e->identityHashCode="+System.identityHashCode(e));
System.out.println("f->identityHashCode="+System.identityHashCode(f));
}
通过调用System.identityHashCode()
方法打印变量的hash值进行分析问题:
run result NO.1->true
a->identityHashCode=1706377736
b->identityHashCode=468121027
run result NO.1->false
c->identityHashCode=1804094807
d->identityHashCode=951007336
run result NO.1->true
e->identityHashCode=2001049719
f->identityHashCode=2001049719
看执行结果发现,NO.1的结果true而变量a、b的hash值却不是相同。最开始的问题还没有找到原因怎么又出现了新的问题呢,先不要着急且往下看。
看看源码的实现
既然涉及到自动拆装箱机制,那么我们看看Integer.valueOf()
的源码是怎么实现的
/**
* Returns an {@code Integer} instance representing the specified
* {@code int} value. If a new {@code Integer} instance is not
* required, this method should generally be used in preference to
* the constructor {@link #Integer(int)}, as this method is likely
* to yield significantly better space and time performance by
* caching frequently requested values.
*
* This method will always cache values in the range -128 to 127,
* inclusive, and may cache other values outside of this range.
*
* @param i an {@code int} value.
* @return an {@code Integer} instance representing {@code i}.
* @since 1.5
*/
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
注释中说到参数i如果在-128~127之间,那么返回值就是从缓存中取出并返回的,反之就通过new Integer(i)的方式返回。源码中的IntegerCache是这样写的:
/**
* Cache to support the object identity semantics of autoboxing for values between
* -128 and 127 (inclusive) as required by JLS.
*
* The cache is initialized on first usage. The size of the cache
* may be controlled by the {@code -XX:AutoBoxCacheMax=<size>} option.
* During VM initialization, java.lang.Integer.IntegerCache.high property
* may be set and saved in the private system properties in the
* sun.misc.VM class.
*/
private static class IntegerCache {
static final int low = -128;
static final int high;
static final Integer cache[];
static {
// high value may be configured by property
int h = 127;
String integerCacheHighPropValue =
sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
if (integerCacheHighPropValue != null) {
try {
int i = parseInt(integerCacheHighPropValue);
i = Math.max(i, 127);
// Maximum array size is Integer.MAX_VALUE
h = Math.min(i, Integer.MAX_VALUE - (-low) -1);
} catch( NumberFormatException nfe) {
// If the property cannot be parsed into an int, ignore it.
}
}
high = h;
cache = new Integer[(high - low) + 1];
int j = low;
for(int k = 0; k < cache.length; k++)
cache[k] = new Integer(j++);
// range [-128, 127] must be interned (JLS7 5.1.7)
assert IntegerCache.high >= 127;
}
private IntegerCache() {}
}
通过源码和注释传递的信息我们清晰的得到,在其static块初始化时就一次性生成了-128到127直接的Integer类型变量存储在cache[]中,对于-128到127之间的int类型,返回的都是同一个Integer类型对象。
这下真相大白了,整个工作过程就是:Integer.class在装载(Java虚拟机启动)时,其内部类型IntegerCache的static块即开始执行,实例化并暂存数值在-128到127之间的Integer类型对象。当自动装箱int型值在-128到127之间时,即直接返回IntegerCache中暂存的Integer类型对象。
为什么这样设计呢?其实Integer.valueOf()的注释方法中就说明了这个原因:缓存频繁请求的值可以产生更多的空间和时间性能(请原谅我翻译的不够专业)
As this method is likely to yield significantly better space and time performance by caching frequently requested values.
假设不缓存,每当要自动拆装箱的时候都需要触发new,在堆中分配内存,就显得太慢了。如果是在死循环等极端情况会有FullGC的潜在风险,影响系统的性能。所以不如预先将那些常用的值提前生成好,自动装箱时直接拿出来返回。哪些值是常用的?就是-128到127了。
为什么缓存范围是-128~127?
说到这里就又有新的问题了,缓存的范围为什么是-128~127?
由于计算机只能识别二进制,即0和1。所以规定第一位是符号位,1表示负数,0表示正数。
正数:原码=反码=补码
负数:反码=原码的所有位(符号位除外)取反
补码=反码+1
而一个字节有8位,第1位是符号位,1代表负数,0代表正数。
所以一个字节:
最小正数二进制是0000 0000=0
最大正数二进制是0111 1111 = 64+32+16+8+4+2+1=127
最大负数二进制是1111 1111 = -1
最小负数二进制是1000 0000→ 反码:1111 1111→ 补码: -{(1+2+4+8+16+32+64)+1} =-(127+1)=-128
这就是为什么缓存范围是-128~127的原因了。
回归问题
说了这么多原理概念,再来说说文章一开始的问题。
Integer c = 128, d = 128;
System.out.println("run result NO.2->"+(c == d));
当执行c == d;
时是先执行自动拆装箱在用==
进行比较的,是通过new Integer(c)
,new Integer(d)
返回结果的所以他们的内存地址是不相等,所以可以使用equals()
来解决用==
判断不相等问题。
int a = 128, b = 128;
System.out.println("run result NO.1->"+(a == b));
基本类型的==
是比较两者之间的值是否相等,int类型定义的变量值在-128~127之间时是直接在缓存中取的,当超出这个区间时就要在栈中重新开辟一个区间存放,这就是为什么a与b相等而内存地址不相等的原因
延展
不仅int,Java中的另外7中基本类型都可以自动装箱和自动拆箱,其中也有用到缓存。见下表:
基本类型 | 装箱类型 | 取值范围 | 是否缓存 | 缓存范围 |
---|---|---|---|---|
byte | Byte | -128 ~ 127 | 是 | -128 ~ 127 |
short | Short | -2^15 ~ (2^15 - 1) | 是 | -128 ~ 127 |
int | Integer | -2^31 ~ (2^31 - 1) | 是 | -128 ~ 127 |
long | Long | -2^63 ~ (2^63 - 1) | 是 | -128 ~ 127 |
float | Float | – | 否 | – |
double | Double | – | 否 | – |
boolean | Boolean | true, false | 是 | true, false |
char | Character | \u0000 ~ \uffff | 是 | \u0000 ~ \u007f |
链接:https://juejin.im/post/5df071256fb9a0163b12b9e6