【JavaSE】深入理解Integer缓存原理

文章目录

  • 1. 前言
  • 2. Integer的缓存常量池
  • 3. Integer类重写的方法
  • 4. Integer的几个比较案例
  • 5. 总结

1. 前言

Integer 类在对象中包装了一个基本类型 int 的值。Integer 类对象包含一个 int 类型的字段。

此外,该类提供了多个方法,能在 int 类型和 String 类型之间互相转换,还提供了处理 int 类型时非常有用的其他一些常量和方法。


2. Integer的缓存常量池

Integer源码中,可以找一个一个名为IntegerCache的私有静态内部类,这个类是用来实现Integer的缓存常量池的。

// Integer类中私有的静态类 承载cache的实现
private static class IntegerCache {
    static final int low = -128;// 最小支持为-128
    static final int high;//最大支持
    static final Integer cache[];// 用来装载缓存  常量池

    static {
        // -128~127 这个范围的整数值是使用最广泛的
        int h = 127;
        // Java6中可以通过调整JVM启动参数来设置最大值
        // 根据应用程序的实际情况 灵活的调整来提高性能
        // JVM 的启动参数 -XX:AutoBoxCacheMax=size 修改最大值
        String integerCacheHighPropValue =
        sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
        if (integerCacheHighPropValue != null) {
            try {
                int i = parseInt(integerCacheHighPropValue);
                // 获取较大者
                i = Math.max(i, 127);
                // 设置最大值不能超过Inter.MAX_VALUE
                h = Math.min(i, Integer.MAX_VALUE - (-low) -1);
            } catch( NumberFormatException nfe) {
                // 如果该值配置错误则忽略该参数配置的值,使用默认范围-128~127
            }
        }
        high = h;
        // 初始化数组容量为127 + 128 + 1(以默认区间为参考)
        cache = new Integer[(high - low) + 1];
        int j = low;
        // 缓存通过for循环来实现,创建范围内的整数对象并存储到cache数组中
        // 程序第一次使用Integer的时候需要一定的额外时间来初始化该缓存
        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() {}
}

该缓存常量池的作用,是为了只要值在-128-127之间,无论是采用Integer a=Integer.valueOf(127)还是Integer a=127,都会在缓存常量池中获取到提前创建好的对象。

所以只要值相同,它们获取到的对象是同一个对象,因此地址也相同。

为什么Integer a=127也适用呢?

像上面这样将Integer 类型的a直接赋值给127,Java会通过自动装箱机制,将int类型转化为Integer类型。

自动装箱机制,其实也就是编译期间,编译器自动调用Integer 类中的valueOf方法

public static Integer valueOf(int i) {
    if (i >= IntegerCache.low && i <= IntegerCache.high)
        return IntegerCache.cache[i + (-IntegerCache.low)];
    return new Integer(i);
}

查看valueOf源码不难看出,首先会判断参数i是否在缓存常量池的最小值low和最大值high之间,如果是,

就会返回常量池中的对应对象。

否则,就会创建新对象。


3. Integer类重写的方法

public int hashCode() {
    return Integer.hashCode(value);
}
public static int hashCode(int value) {
    return value;
}

Integer重写了hashCode方法,调用hashCode方法返回值为本身对象的value。

public boolean equals(Object obj) {
    if (obj instanceof Integer) {
        return value == ((Integer)obj).intValue();
    }
    return false;
}

public int intValue() {
    return value;
}

Integer重写了equals方法

调用equals方法,首先会判断参数Object obj是否是Integer类型

如果是,则会将obj强转成Integer,然后取其对象中的value,跟自己的value进行判断。

如果不是,则返回false


4. Integer的几个比较案例

两个Integer对象进行比较

public static void main(String[] args) {
    Integer a = new Integer(66);
    Integer b = new Integer(66);
    System.out.println(a == b);
}

因为a和b均为新new出来的对象,并且==比较的是地址,两个new出来的对象永远不可能相等,因此输出false

两个Integer对象使用equal方法进行比较

public static void main(String[] args) {
    Integer a = new Integer(66);
    Integer b = new Integer(66);
    System.out.println(a.equals(b));
}

上面有说过使用equal方法,如果两个比较的对象均为Integer,则比较它们的value是否相等,很明显,它们的value相等,所以是true

基本类型和Integer类型 通过 == 比较

public static void main(String[] args) {
    Integer a = new Integer(66);
    int b = 100;
    System.out.println(a == b);
}

这里,基本类型和Integer类型比较,Java会先自动进行拆箱操作,也就是将包装类型转化为基本类型,然后再进行比较,所以比较的是两个int值是否相同,答案是true

在缓存范围内的比较

public static void main(String[] args) {
    Integer a = 66;
    Integer b = 66;
    System.out.println(a == b);
}

对于这种直接将两个int类型赋值给Integer,Java会先进行自动装箱,也就是调用valueOf方法。

由于66在[-127,128]范围内,也就是默认的缓存范围,所以这里不会新创Integer对象,而是生成的对象引用都会指向Integer内部的常量池中对应的值的对象。所以两个对象为同一对象。因此答案是true

在缓存范围外的比较

public static void main(String[] args) {
    Integer a = 128;
    Integer b = 128;
    System.out.println(a == b);
}

同样的,Java会进行装箱操作,但是由于值不在[-127, 128]范围内,因此valueOf方法会返回一个new Integer(i);,因此两个对象并不是指向常量池对应的值的对象,故不是同一对象。答案是false

new生成的Integer对象与直接赋值的Integer对象的比较

public static void main(String[] args) {
    Integer a = new Integer(66);
    Integer b = 66;
    System.out.println(a == b);
    System.out.println(a.equals(b));
}

首先,a是new出来的对象,而b是指向Integer内部的常量池中对应的值的对象。

两者明显不是一个对象,只是两个对象的value相同而已。

所以答案是falsetrue


5. 总结

首先,不仅仅是Integer类型有缓存常量池这个说法。

Byte,Short,Integer,Long,Character都有缓存常量池。

  1. Byte,Short,Integer,Long为 缓存范围 -128 到 127
  2. Character缓存范围为 0 到 127
  3. 只有Integer缓存范围可以改变

其次,开发中应该尽量避免多次装箱开箱操作。

int值直接存放于内存栈中,虽然数值操作不是原子性的,但是在单线程的存取操作中,开销小,所以速度快很多,不像Integer**,还需要牵扯对象头的内存存储以及其他操作,更加的消耗性能**。


你可能感兴趣的:(深入JavaSE,缓存,jvm,java)