常见知识点和易错点:Integer的缓存问题
我本人接触学习Java基础的过程是比较短暂的,以至于我现在回顾的时候甚至感觉那段学习过程是草率而糟糕的。并且一些常见的考题虽然看过无数遍,但是用不了多久就忘得一干二净问题还是没有得以解决,或者说理解地不够透彻换个例子就成了”新问题“,所以我决定通过笔记的形式来记录下这些基础部分的常见问题与易错题。
笔记中可能存在大量问题,希望各位看官不吝赐教,大胆指出相互学习!
一、Integer的缓存问题
众所周知Java的Integer类是有缓存的!这部分缓存值会在JVM启动时放入运行时常量池中,这个缓存的范围是:
-128~127
。但是它的存在会带来什么影响呢? 下面我们慢慢展开来说。
静态内部类IntegerCache
首先我们打开Integer的源码,我们先要明确这个缓存范围是怎么来的:
-
我们搜索
IntegerCache
,会找到一个静态内部类:private static class IntegerCache { static final int low = -128; static final int high; static final Integer[] cache; static Integer[] archivedCache; static { // high value may be configured by property int h = 127; String integerCacheHighPropValue = 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; // Load IntegerCache.archivedCache from archive, if possible VM.initializeFromArchive(IntegerCache.class); int size = (high - low) + 1; // Use the archived cache if it exists and is large enough if (archivedCache == null || size > archivedCache.length) { Integer[] c = new Integer[size]; int j = low; for(int k = 0; k < c.length; k++) c[k] = new Integer(j++); archivedCache = c; } cache = archivedCache; // range [-128, 127] must be interned (JLS7 5.1.7) assert IntegerCache.high >= 127; } private IntegerCache() {} }
我们简单看看代码吧,在我们没有事先对
IntegerCache.high
做修改的话,其默认值就是127
。所以范围基本就定为了-128~127。然后看看如何完成缓存的:
首先是两个
Integer
数组cache
、archivedCache
.- 先是从存档中加载已存档的IntegerCache,如果存档存在且足够大,则直接将archivedCache赋值给cache进行使用。
- 否则,会创建一个大小为256的Integer数组
c
,然后将从-128到127的所有整数使用new Integer(int)
的方式创建好Integer实例放入数组c
,然后将c
赋值给archivedCache
,最后还是将archivedCache赋值给cache。
至此我们的缓存就初始化结束了,-128到127对应的实例都已经加入到常量池中就绪了!
-
推荐读一下类注释
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
-XX:AutoBoxCacheMax=<size>
option. During VM initialization,java.lang.Integer.IntegerCache.high
property may be set and saved in the private system properties in thejdk.internal.misc.VM
class.附上机翻:
缓存以支持 JLS 要求的 -128 和 127(含)之间值的自动装箱的对象标识语义。 缓存在第一次使用时初始化。 缓存的大小可以由
-XX:AutoBoxCacheMax=<size>
选项控制。 在VM初始化过程中,java.lang.Integer.IntegerCache.high
属性可能会被设置并保存在jdk.internal.misc.VM
类的私有系统属性中。
Cache验证
然后我们就要来验证一下,是否我们的程序运行前常量池中就有了这256个值:
-
首先我们写一个最最最简单的java程序,然后在第一行代码上打上断点进行调试,然后打开Memory面板
-
先前我们在阅读代码的时候看到256个Integer实例都是放在一个名为cache的Integer数组中,所以我们直接查找Integer数组即可
(以下运行结果是在Linux环境下运行的,使用Windwos可能会有些出入【会多出几个Integer对象】) -
我想应该不用我再说下去了。。。。你自个儿点开看吧。
常见问题与易出错点
后面我们就要说说,这个缓存引出的一些常见易错点:
首先来看一个常见的关于Integer缓存的问题。
查看以下代码,给出运行结果:
public static void main(String[] args) {
int i0 = 18;
Integer i1 = 18;
Integer i2 = 18;
Integer i3 = Integer.valueOf(18);
Integer i4 = new Integer(18);
System.out.println(i0 == i1);
System.out.println(i0 == i3);
System.out.println(i0 == i4);
System.out.println(i1 == i2);
System.out.println(i1 == i3);
System.out.println(i1 == i4);
System.out.println(i3 == i4);
}
下面给出答案:
true
true
true
true
true
false
false
案例代码分析
我们先不急来分析结果,先来通过调试来看看这段代码的执行过程(准确说是对象创建过程)。
- i0 -> int赋值,不多说
- i1, i2 -> Integer对象传值,这里我们调试的时候强制进入时,会调到Integer类的
valueOf(int)
方法然后返回一个Integer实例。这也就是我们常听到的Java自动封箱。 - i3 -> 使用
Integer.valueOf(int)
获得Integer对象,我们稍后来看看valueOf(int)
代码实现。 - i4 -> 使用
new Integer(int)
,通过构造器创建Integer对象。
到此所有变量已经赋值完毕,此时我们看向我们的调试面板的变量列表。可以发现i1、i2、i3
居然是引用的同一个对象!而i4
则是一个新的Integer对象:
而他们的创建过程则很好地解释了这个现象:i1 i2 i3
都是使用valueOf(int)
获取的Integer对象,而i4
则是使用new Integer(int)
。那么现在解开谜题的关键就是valueOf(int)
的代码了。
我们按照常例,先来读一下源码上的注释信息:
Returns an Integer instance representing the specified int value. If a new Integer instance is not required, this method should generally be used in preference to the constructor
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.
Params: i – an int value.
Returns: an Integer instance representing i.
大概就是,使用这种方式会直接返回一个指定值的Integer实例对象,这种返回实例的方式在不需要创建新Integer实例的情况下,会优先于使用Integer(int)
构造器。如果是频繁地取值使用这种(缓存)方式会表现出更好的空间和时间性能。这个方法的缓存值范围是-128至127,并且可能会包含在这个范围之外的值!(例如其他依赖包中事先加载的常量。)
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
哦!这里联系到了我们前面查看过的静态内部类IntegerCache
!我们来看看IntegerCache提前创建好的放在cache
数组中的256个Integer对象实例是如何起到缓存作用的。
emmmm, 貌似也不用多说是非常简单的范围判断逻辑,如果指定的值i
在缓存范围(-128~127)内则直接从cache
数组中取出对应的已经提前准备好的Integer对象返回给调用者!否则才使用new Integer()
创建一个新的Integer对象返回!
我们再回看为什么i1 i2 i3指向同一个对象?
这个问题,其实答案已经浮出水面了~(如果你想验证一下valueOf方法与IntegerCache工作原理,你可将我们案例中的数字18替换为任意缓存范围外的数字)。
自动拆包
其实讲到这里,上面那个案例的大部分答案都可以给出解释了,但是一个关于i0
的问题需要补充一下!
如果说按照创建的方式来划分阵营,那么i1 i2 i3
为同一个阵营,i4
一个阵营;而i0
压根都不是一个对象!所以说按照类型来划分i1 i2 i3 i4
成为了同一个阵营的好伙伴。所以他们在与i0
进行比较时,就遇到一个问题:咱们俩都不是一个级别的东西怎么比?比引用对象地址?(i0: 我可没这玩意儿,我只有一个数值)。那就只能“委屈”他们将自己”解剖“将对应的数值拿出来和i0
比较了。【这个”解剖“的过程就是常听到的自动拆包】
通过调试,可以发现在执行i0 == i1
时,i1会调用intValue()
将自己(Integer对象)拆包为一个基本类型数据!然后进行数值比较!i4
也是同理,所以与基本类型数据进行比较时,对象类型变量都要进行自动拆包,进行数值比较!!
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步