Integer缓存那点小事儿

211 阅读5分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 2 天,点击查看活动详情

Integer 面试中我们经常被问到,另外工作中我们也经常遇到Integer的比较,所以今天来快速总结下这个知识点。

1. Integer i=200; 会发生什么?

首先我们假设声明了两个局部变量

Integer i=200;
Integer j=100;

事实上,Java最终的逻辑是不管你后边的数是多少,最终都会调 Integer.valueOf(i);来将给定的数值,进行 装箱 操作,但是里边会判断,就是如果给定数值 i

  • 是在-127-128之间的数值,那么将会从一个Integer类型的缓存数组中 拿到指定位置(即index是给定数值的)对应的Integer对象, 如果给定数值i不在 -127-128范围内,那么
  • 将会在堆上开辟空间,new一个Integer对象出来

空口无凭,我们使用命令反汇编一下,看看到底是是不是会像上边说的那样; 执行命令:

Javap -p -v -c target/test-classes/com/xzll/test/mianshi/IntegerCacheTest.class

结果如下: image.png

2. IntegerCache初始化源码&&valueOf简析

我们看下这个逻辑,Integer类源码如下:

public final class Integer extends Number implements Comparable<Integer> {
private static class IntegerCache {
    //下界
    static final int low = -128;
    //上界,可通过  -XX:AutoBoxCacheMax=size  来调整大小,但是不能超过Inter.MAX,并且小于127的话,就还是取127
    static final int high;
    //这个就是存储Integer对象的数组了
    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
        // 新建一个Integer数组,初始值为-128, -127, ..., 0, 1, 2, ..., 127
        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变量
            archivedCache = c;
        }
        //赋值给 cache变量
        cache = archivedCache;
        // range [-128, 127] must be interned (JLS7 5.1.7)
        assert IntegerCache.high >= 127;
    }

    private IntegerCache() {}
}


public static Integer valueOf(int i) {
    if (i >= IntegerCache.low && i <= IntegerCache.high)
        return IntegerCache.cache[i + (-IntegerCache.low)];//不new object,而是直接从cache中读出object返回
    return new Integer(i);//new 一个Integer对象返回
}

    
}

上边代码比较简单,我们不过多展开了

3. 演示一下各种对比以及结果:

public static void main(String[] args) {

   int i1 = 200;
   Integer i2 = 200;
   System.out.println("i1 == i2:结果:" + (i1 == i2) + ",// int和new出来的Integer (因为200不在(-127-128)区间,所以会new对象 )比较,i2会自动拆箱 所以会变成两个int的比较");

   Integer i3 = 200;//编译后,此处变为 -> Integer i3 = Integer.valueOf(200); 也就是说不会从IntegerCache中获取,而是new了一个对象
   Integer i4 = 200;
   System.out.println("i3 == i4:结果:" + (i3 == i4) + ",// 由于200不在缓存 -127-128之间,所以此处是两个对象的比较,i3内存地址:" + System.identityHashCode(i3) + " ,i4内存地址:" + System.identityHashCode(i4));


   Integer i15 = new Integer(100);
   Integer i16 = new Integer(100);
   System.out.println("i15 == i16:结果:" + (i15 == i16) + ",// i15和i16是new的两个对象,i15内存地址:" + System.identityHashCode(i15) + " ,i16内存地址:" + System.identityHashCode(i16));

   Integer i17 = 100;
   System.out.println("i15 == i17:结果:" + (i15 == i17) + ",// 由于 i15是new的对象,i17是从Integer缓存数组中拿的对象,两个都是对象,i15内存地址:"+System.identityHashCode(i15)+",i17内存地址:"+System.identityHashCode(i17));

   Integer i18 = 100;
   System.out.println("i17 == i18:"+"结果:" + (i17 == i18) + ",// 由于100 在缓存 -127-128之间,所以此处是两个相同对象的比较(因为都是从数组中拿的同一个index位置的Integer对象),i17内存地址:" + System.identityHashCode(i17) + " ,i18内存地址:" + System.identityHashCode(i18));

   int i100=100;
   System.out.println("i5==i100:结果:"+(i15==i100)+",// 由于i100是基础类型,和从Integer缓存中拿出来的i17(Integer对象比较)比较,i17会自动拆箱,结果自然是true");
}

输出:

i1 == i2:结果:true,// int和new出来的Integer (因为200不在(-127-128)区间,所以会new对象 )比较,i2会自动拆箱 所以会变成两个int的比较
i3 == i4:结果:false,// 由于200不在缓存 -127-128之间,所以此处是两个对象的比较,i3内存地址:706197430 ,i4内存地址:1325808650
i15 == i16:结果:false,// i15和i16是new的两个对象,i15内存地址:510464020 ,i16内存地址:1987083830
i15 == i17:结果:false,// 由于 i15是new的对象,i17是从Integer缓存数组中拿的对象,两个都是对象,i15内存地址:510464020,i17内存地址:1632492873
i17 == i18:结果:true,// 由于100 在缓存 -127-128之间,所以此处是两个相同对象的比较(因为都是从数组中拿的同一个index位置的Integer对象),i17内存地址:1632492873 ,i18内存地址:1632492873
i5==i100:结果:true,// 由于i100是基础类型,和从Integer缓存中拿出来的i17(Integer对象比较)比较,i17会自动拆箱,结果自然是true

(idea运行)附图如下:

image.png

3.1 做个小总结

  • 无论如何,Integer i=xxx; 与new Integer(xxx) 不会相等。不会经历拆箱过程

  • 两个都是非new出来的Integer对象(即Integer i=xxx ),如果xxx 在-128到127之间,则是true,否则为false

  • 两个都是new出来的,都为false

  • int和Integer(无论new否)比,都为true,因为会把Integer自动拆箱为int再去比

4. 事实上,不仅Integer这么做,其他也有类似做法,如下

  • 有 ByteCache 用于缓存 Byte 对象
  • 有 ShortCache 用于缓存 Short 对象
  • 有 LongCache 用于缓存 Long 对象
  • 有 CharacterCache 用于缓存 Character 对象

Byte,Short,Long 有固定范围: -128 到 127。对于 Character, 范围是 0 到 127。除了 Integer 可以通过参数改变范围外,其它的都不行。

可以看出来jdk的大佬们是多么的追求极致!

一天一小步,一年一大步!


ps:今晚是通宵上线的一晚,请bug远离我~~~