【Integer】java.lang.Integer 类的缓存机制与享元模式

1. 缓存机制与享元模式

此文简单介绍下关于 Integer 类的 缓存机制 ,这是在 Java 5 中引入的一个既能节省内存又能提高性能的功能。

简单地看一个示例代码,来体会下 Integer 类中的缓存机制:

Integer a = 1;
Integer b = 1;
Integer c = 200;
Integer d = 200;

System.out.println(a == b);  // true
System.out.println(c == d);  // false

看到这几行代码时就有些疑惑了:同样是判断引用是否相等,为什么一个为 true,另一个为 false 呢?

java.lang.Integer 类运用了享元模式,使之能复用数值相同的 Integer 对象。当创建一个 Integer 对象时,如果常量池中有此 Integer 对象,则直接从常量池中取出来;否则,就 new 一个 Integer 对象。

既然如此,变量 a、b 相等我能理解,但是 c、d 为什么不相等呢?暂且先留着这个悬念吧。咋们接下来先了解下享元模式。

享元模式(一种设计模式):运用共享技术有效地支持大量细粒度的对象。

作用:享元模式主要用于减少创建对象的数量,以减少内存占用和提高性能

应用场景:
池技术:String 常量池、数据库连接池、缓冲池等

实现:
享元工厂根据对象的外部状态来创建享元对象,其中,使用一个 池容器来存储创建的享元对象。创建对象时,如果该对象存在 池容器 中,则直接从容器中取出;否则,new 一个对象,再放进容器中。

2. 缓存机制的原理

揭秘:为什么 a、b 相等;c、d 不相等

看上面示例代码的前 4 行语句 ------ 它们都会进行 自动装箱 操作,那么这就会调用 Integer.valueOf() 方法(通过 反编译、Debug 模式 这两种方法验证)

反编译后:

Integer a = Integer.valueOf(1);
Integer b = Integer.valueOf(1);
Integer c = Integer.valueOf(200);
Integer d = Integer.valueOf(200);

查看 JDK1.8 Integer.valueOf() 方法源码:

public static Integer valueOf(int i) {
	// -128 <= i <= 127 时,直接从静态内部类 IntegerCache 中的 []cache 数组中获取
    if (i >= IntegerCache.low && i <= IntegerCache.high)
        return IntegerCache.cache[i + (-IntegerCache.low)];
    // 否则,就重新 new 一个 Integer 对象
    return new Integer(i);
}

查看 IntegerCache 类源码:

// 使用亨元模式,来减少对象的创建
// IntegerCache 类相当于享元工程类;[]cache 数组相当于池容器
private static class IntegerCache {
	// 最小值
    static final int low = -128;
    // 最大值(可修改) 默认:127
    static final int high;
    static final Integer cache[];

	// 静态代码块,类加载时就执行
    static {
    	// 默认最大值
        int h = 127;
        String integerCacheHighPropValue =
            sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
        // 如果设置了最大值(不能超过 Integer.MAX_VALUE),则将最大值与 127 进行比较
        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) {
                ...
            }
        }
        // 赋值最大值
        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() {}
}

【注意】:

  1. IntegerCache 类是一个静态内部类。那么,静态内部类的加载不需要依附外部类,它在使用时才会加载
  2. cache[]:cache 是常量数组,位于方法区中。

总结:IntegerCache 类内部实现了一个 Integer 类型的静态常量数组 []cache。在类加载的时候,会执行 static静态代码块,初始化 -128 到 127 之间的 Integer 对象,然后存放到 []cache 数组中

所以,上述中知道了为什么 a、b 相等,而 c、d 不相等了吧?

Integer 对象 1 在 [-128, 127] 之间,则直接从缓存中去,那么它们的地址必然相等;Integer 对象 200 大于 127,不在 [-128, 127] 之间,则两个对象都是 new 出来的,存储在堆中,那么它们的地址必然不相等。

那有没有想过为什么是 [-128,127] 之间的范围,而不是其它数值的范围?

因为这个范围的数字是最被广泛使用的(防止每次自动装箱都创建一此对象的实例);而且,在第一次使用 Integer 的时候也需要一定的时间、资源来初始化这个缓存数组 []cache。

创建 Integer 类型的对象,有三种,但优先使用后两种:

  1. Integer a = new Integer(123);
  2. Integer b = 123;
  3. Integer c = Integer.valueOf(123);

第一种创建方式并不会使用到 IntegerCache,而后面两种创建方法可以利用 IntegerCache 缓存,返回共享的对象,以达到节省内存的目的。如:创建 100 个 -128 到 127 之间的 Integer 对象。如果使用第一种创建方式,我们需要分配 100 个 Integer 对象的内存空间;使用后两种创建方式,我们最多只需要分配 256 个 Integer 对象的内存空间。所以,使用到 IntegerCache 缓存,可以节省内存空间。

3. 其它类型的自动装箱代码

咋们看看另外一个示例代码吧:
Double 类型:

Double d1 = 1.00;
Double d2 = 1.00;
System.out.println(d1 == d2);  // false

上面的执行结果很奇怪,竟然跟 Integer 类型不一样。查看 Double.valueOf() 方法:

public static Double valueOf(double d) {
    return new Double(d);
}

这个 valueOf() 方法的实现就很简洁,直接 new 对象,没有进行缓存。

那么为什么不把 Integer 类型和 Double 类型中的 valueOf() 方法统一呢?换句话说,Double 类型中的 valueOf() 方法为什么不进行缓存呢?

因为:对于 Integer 类型而言,[-128,127] 之间只有固定的 256 个值,为了避免多次创建数值相同的对象,我们事先就创建好一个大小为 256 的Integer数组(cache = new Integer[(high - low) + 1];)所以,如果值在这个范围内,就可以直接返回我们事先创建好的对象就可以了;但是,对于 Double 类型而言,我们根本就不能这样做,因为它在这个范围内个数是无限的。
一句话:在某个范围内的整型数值的个数是有限的,而浮点数却不是。

看看其它整形类型的 valueOf() 方法:

// byte 原生类型自动装箱成 Byte
public static Byte valueOf(byte b) {
  final int offset = 128;
  return ByteCache.cache[(int)b + offset];
}
// short 原生类型自动装箱成 Short
// char 原生类型自动装箱成 Character
// long 原生类型自动装箱成 Long

这些都与 Integer 类型类似。

Boolean 类型:

Boolean b1 = false;
Boolean b2 = false;

Boolean b3 = true;
Boolean b4 = true;
System.out.println(b1 == b2);  // true
System.out.println(b3 == b4);  // true

Boolean.valueOf() 方法

public static Boolean valueOf(boolean b) {
    return (b ? TRUE : FALSE);
}

执行 valueOf() 方法后,返回的是相同的对象

查看 TRUE、FALSE 对象:

public static final Boolean TRUE = new Boolean(true);

public static final Boolean FALSE = new Boolean(false);

可以看到,它并没有创建对象,因为在内部已经提前创建好两个对象。因为它只有两种情况,这样也是为了避免重复创建太多的对象。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值