美文网首页Java源码
深入分析Integer底层原理

深入分析Integer底层原理

作者: Oceans言欢 | 来源:发表于2019-05-30 20:16 被阅读0次

本文将介绍Java中Integer的相关知识,分析Integer缓存的原理和作用。

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() {}
}

该缓存的作用是为了节省内存,提高性能。
在给一个Integer对象直接赋一个int类型的值得时候:

Integer num = 100;

Java会通过自动装箱机制将int类型值转为Integer类型。自动装箱是指在编译时期编译器自动调用包装类型中的valueOf()方法,进行自动的转换。

Integer#valueOf方法的实现

public static Integer valueOf(int i) {
    // 如果命中缓冲区的范围则直接返回已有对应对象的引用
    if (i >= IntegerCache.low && i <= IntegerCache.high)
        return IntegerCache.cache[i + (-IntegerCache.low)];
    // 不在缓冲范围内的直接new新的对象
    return new Integer(i);
}

针对[-128,127]这个范围内的值,Integer会通过复用对象的方式来提升内存的使用率,减少新对象的生成。

Intege类重写了equals方法和hashCode方法

hashCode()

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

即Integer的hashCode值返回对象本身的value值

equals()

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

equals方法比较的是两个对象的value值,即两个Integer对象只要逻辑上数值一致,则equals方法返回true。

常见的使用场景

通过分析六种不同类型的比较,进而真正弄懂Integer与int的区别,这也是面试中经常会问到的地方。

1.两个Integer对象 通过 == 比较

Integer a = new Integer(100);
Integer b = new Integer(100);
System.out.println(a == b); // 输出false

通过new生成的两个对象是永远不相等的,调用==时比较的是两个引用指向的内存地址。

2.两个对象 通过equals方法比较

Integer a = new Integer(100);
Integer b = new Integer(100);
System.out.println(a.equals(b)); // 输出true

当调用equals方法进行比较时,只要两个对象的值相同,则返回true。Integer重写了equals方法,核心逻辑是判断对象表示的value值是否相同。

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

Integer a = new Integer(100);
int b = 100;
System.out.println(a == b); // 输出true

当包装类型和基本类型进行比较时,Java会进行自动拆箱,将包装类型拆为对应的基本类型,然后进行比较,实际上比较的两个int值是否相同。

4.在缓存范围内的比较

Integer a = 100;
Integer b = 100;
System.out.println(a == b); // 输出true
System.out.println(a.equals(b)); // 输出true

对应非new生成的Integer对象,Java会通过自动装箱机制,调用Integer的valueOf方法将int类型转为Integer类型。如果该int值在[-128,127]区间范围内,则生成的对象引用都会指向Integer内部的常量池中对应的值的对象,因此两个引用指向的内存地址也相同即==时返回true,调用equals时比较的是value值,因此也返回true。

5.在缓存范围外的比较

Integer a = 128;
Integer b = 128;
System.out.println(a == b); // 输出false
System.out.println(a.equals(b));//输出true

非new生成的Integer对象,如果int值超出了[-128,127]这个区间,则不会使用常量池中的对象,由valueOf方法可知会重新new一个Integer对象。因此两个引用a和b,调用==比较时是比较的对象内存地址,所以返回false;但是调用equals方法比较的是value值,因此返回true。

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

Integer a = new Integer(100);
Integer b = 100;
System.out.println(a == b); //输出false
System.out.println(a.equals(b));// 输出true

当非new生成的对象和new生成的对象进行比较时,非new生成的对象根据其value值是否在默认缓存区间内而选择是否复用对象,通过new生成的对象是在堆中重新开辟的内存空间,因此两者指向的内存地址肯定不一样,所以调用==时返回false;equals方法比较对象的逻辑值value,因此返回true。

开发中常见的建议

  • int是基本数据类型,只占用4个字节,Integer是一个对象,当表示一个值时Integer占用的内存空间要高于int类型,从节省内存空间考虑,建议使用int类型。
  • Integer类型必须进行初始化后才能使用,否则会引起NullPointerException异常。
  • Integer对象默认值为null,int默认值为0。
  • 针对一些特殊的场景比如考试成绩分为没有参加考试和成绩为0,int的默认值为0,显然不合适这种场景;Integer可以区分出未赋值和值为0的区别。
  • 使用Integer类型时,建议采用直接赋值的形式而不是通过new产生新对象,提高对内存的利用率。
Integer a = 100;
替代
Integer a = new Integer(100);
  • 当程序中大量使用数值时,可以根据实际情况适当扩展常量池缓冲区的区间上限,
    修改JVM的启动参数: -XX:AutoBoxCacheMax=size ,进而节省内存,提升性能。
  • 当使用Integer类型时,在进行两个对象比较的时候,推荐使用equals方法,而不是直接调用"=="。
阿里巴巴开发手册中提到:
包装类型间的相等判断应该用equals,而不是'=='
所有的包装类对象之间值的比较,全部使用equals方法比较。
 说明:对于Integer var=?在-128至127之间的赋值,Integer对象是在IntegerCache.cache产生,会复用已有对象,这个区间内的Integer值可以直接使用==进行判断,但是这个区间之外的所有数据,都会在堆上产生,并不会复用已有对象,这是一个大坑,推荐使用equals方法进行判断。

其他缓存的对象

这种缓存行为不仅适用于Integer对象。我们针对所有整数类型的类都有类似的缓存机制。
Byte,Short,Long 有固定范围: -128 到 127。对于 Character, 范围是 0 到 127。除了 Integer 可以通过参数改变范围外,其它的都不行。
Double类的valueOf方法会采用与Integer类的valueOf方法不同的实现。很简单:在某个范围内的整型数值的个数是有限的,而浮点数却不是。
注意,Integer、Short、Byte、Character、Long这几个类的valueOf方法的实现是类似的。Double、Float的valueOf方法的实现是类似的。

相关文章

网友评论

    本文标题:深入分析Integer底层原理

    本文链接:https://www.haomeiwen.com/subject/wxrbtctx.html