4

Byte、Short、Integer、Long内部缓存类的对比与源码分析

 2 years ago
source link: https://blog.51cto.com/u_15724795/5542940
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

Byte、Short、Integer、Long内部缓存类的对比与源码分析

推荐 原创

这是 《水煮 JDK 源码》系列 的第7篇文章,计划撰写100篇关于JDK源码相关的文章

对于基本数据类型的包装类 ByteShortIntegerLong ,其内部实现都有一个缓存类,这个缓存类主要用于缓存固定区间的数值对象,默认为 [-128, 127],其中 Integer 的缓存区间最大值可以通过属性动态配置,而 ByteShortLong 则不能动态配置。

在平时的开发过程中,可能对于这些包装类的内部缓存没有过多的关注,如果没有阅读过相关源码,也可能完全不知道还有内部缓存类的存在,但是下面的代码所展示的或许见过。

public static void main(String[] args) {
    Integer num1 = 100;
    Integer num2 = 100;
    System.out.println("num1 与 num2 是否相等:" + (num1 == num2));

    Integer num3 = 128;
    Integer num4 = 128;
    System.out.println("num3 与 num4 是否相等:" + (num3 == num4));
}

运行上面的代码,输出结果是什么呢?了解过 Integer 内部缓存池的可能会给出如下的结果

num1 与 num2 是否相等:true
num3 与 num4 是否相等:false

但是在此处,我想说的是,这个结果不完全正确,为什么呢?因为漏掉了另外一种可能的情况,准确来说,上面的程序运行可能会出现两种结果:

  • 未改变 Integer 缓存区间最大值时,默认缓存区间为 [-128, 127],此时上面程序输出结果如下

    num1 与 num2 是否相等:true
    num3 与 num4 是否相等:false
    
  • 通过 java.lang.Integer.IntegerCache.high 属性改变了 Integer 缓存区间的最大值时,比如改变后的缓存区间为 [-128, 200],那么此时上面程序的输出结果就不一样了,如下

    num1 与 num2 是否相等:true
    num3 与 num4 是否相等:true
    

为什么会这样呢?上面的程序定义涉及到 intInteger 之间的装箱操作,而装箱操作是在编译时自动完成的,具体装箱操作调用的是 IntegervalueOf() 方法,而 valueOf() 方法的实现又与 Integer 的内部缓存类 IntegerCache 的实现有关,IntegerCache 的缓存区间最大值是可以动态配置的

那么对于 ByteShortLong 是否也是一样的呢?答案是否定的,也就是说,如果上面程序的类型改成 ByteShortLong 任意一种,其输出结果只会有一种,也就是对应 Integer 的第一种结果,下面来具体分析各个类的内部缓存类的源码实现,在分析完后,或许就有了不一样的理解和认识。

1、Byte 的内部缓存类 ByteCache

private static class ByteCache {
    private ByteCache(){}

    // 定义了一个大小为 256 的 Byte[] 数组缓存
    static final Byte cache[] = new Byte[-(-128) + 127 + 1];

    // 静态代码块用于初始化 Byte[] 数组缓存
    static {
        for(int i = 0; i < cache.length; i++)
            cache[i] = new Byte((byte)(i - 128));
    }
}

ByteCacheByte 的内部静态类,由于构造函数为私有的,所以不能被实例化,缓存类中只有一个成员变量 cache,它是一个大小为 256 的 Byte 数组,静态代码块初始化时,会向数组中填充 256个 Byte 对象,这些 Byte 对象对应的数值是 [-128, 127],下面再来看看 BytevalueOf() 方法的实现。

public static Byte valueOf(byte b) {
    // 偏移量为 128
    final int offset = 128;
    // 这里直接从 ByteCache 的缓存数组中获取 Byte 对象
    return ByteCache.cache[(int)b + offset];
}

public static Byte valueOf(String s, int radix)
    throws NumberFormatException {
    // 调用的还是上面的方法
    return valueOf(parseByte(s, radix));
}

public static Byte valueOf(String s) throws NumberFormatException {
    return valueOf(s, 10);
}

虽然 Byte 类提供了3个 valueOf() 方法,但是通过分析可知,最终调用的都是第一个方法,即 valueOf(byte b) 这个,由于 byte 能表示的数值区间为 [-128, 127] ,所以如果是直接通过同一数值定义多个 Byte ,这些 Byte 实例对象都是相同的,如下:

public static void main(String[] args) {
    Byte byte1 = 1;
    Byte byte2 = 1;
    Byte byte3 = 1;

    System.out.println(byte1 == byte2);
    System.out.println(byte2 == byte3);
}

输出的结果如下:

true
true

那么如果换一种方式定义 byte1byte2byte3,结果又会怎么样呢?

public static void main(String[] args) {
    Byte byte1 = new Byte("1");
    Byte byte2 = new Byte("1");
    Byte byte3 = new Byte("1");
    
    System.out.println(byte1 == byte2);
    System.out.println(byte2 == byte3);
}

运行程序输出结果如下:

false
false

因为当直接使用 new 关键字创建对象时,会直接在堆上分配新的内存空间,同时 new Byte("1") 的实现和 valueOf() 并不一样,所以 创建的 byte1byte2byte3 对象并不相同。

2、Short 的内部缓存类 ShortCache

private static class ShortCache {
    private ShortCache(){}

    // 定义了一个大小为 256 的 Short[] 数组缓存
    static final Short cache[] = new Short[-(-128) + 127 + 1];

    static {
        for(int i = 0; i < cache.length; i++)
            cache[i] = new Short((short)(i - 128));
    }
}

ShortCache 缓存类和 ByteCache 缓存类很类似,缓存数值的大小都是 256,缓存数值区间都是 [-128, 127],只是数值的类型不一样,下面再来看看 Short 类的 valueOf() 方法实现。

public static Short valueOf(short s) {
    final int offset = 128;
    int sAsInt = s;
    // 如果 s 值在 [-128, 127] 之间,则直接返回 ShortCache.cache[] 中已缓存的对象
    if (sAsInt >= -128 && sAsInt <= 127) { // must cache
        return ShortCache.cache[sAsInt + offset];
    }
    
    // 不在 [-128, 127] 之间时,直接使用 new Short() 创建对象
    return new Short(s);
}

public static Short valueOf(String s) throws NumberFormatException {
    return valueOf(s, 10);
}

public static Short valueOf(String s, int radix)
    throws NumberFormatException {
    return valueOf(parseShort(s, radix));
}

Byte 类一样,Short 类也有3个 valueOf() 方法,只是 Short 类的 valueOf() 方法实现却不相同,主要由于 Short 类能表示的数值区间为 [-32768, 32767],而 ShortCache 缓存的数值区间只有 [-128, 127],所以只有当数值在 [-128, 127] 之间时,才能从缓存中复用对象,超过这些数值的,缓存中也没有。

3、Long 的内部缓存类 LongCache

private static class LongCache {
    private LongCache(){}

    // 定义了一个大小为 256 的 Long[] 数组缓存
    static final Long cache[] = new Long[-(-128) + 127 + 1];

    static {
        for(int i = 0; i < cache.length; i++)
            cache[i] = new Long(i - 128);
    }
}

LongCache 的实现和 ShortCacheByteCache 并无区别,只是类型不一样而已,下面来看看其 valeuOf() 的实现

public static Long valueOf(long l) {
    final int offset = 128;
    // 如果 l 值在 [-128, 127] 之间,则直接返回 LongCache.cache[] 中已缓存的对象
    if (l >= -128 && l <= 127) { // will cache
        return LongCache.cache[(int)l + offset];
    }
    return new Long(l);
}

public static Long valueOf(String s) throws NumberFormatException {
    return Long.valueOf(parseLong(s, 10));
}

public static Long valueOf(String s, int radix) throws NumberFormatException {
    return Long.valueOf(parseLong(s, radix));
}

可以看出,Long 类的 valueOf() 方法和 Short 类的 valueOf() 方法是一样的。

4、Integer 的内部缓存类 IntegerCache

之所以把 IntegerCache 放在最后分析,是因为 IntegerCache 的实现和其他的都不太一样,具体源码如下:

private static class IntegerCache {
    // 最小值 -128
    static final int low = -128;
    // 最大值
    static final int high;
    // 缓存数组
    static final Integer cache[];

    static {
        // 最大值默认为 127,也可以通过属性字段配置,具体属性名为 java.lang.Integer.IntegerCache.high
        int h = 127;
        // 获取 java.lang.Integer.IntegerCache.high 属性值
        String integerCacheHighPropValue =
            sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
        // 如果属性值不为空
        if (integerCacheHighPropValue != null) {
            try {
                // 使用 parseInt() 方法将属性值转换为 int 类型
                int i = parseInt(integerCacheHighPropValue);
                // 取属性值与127两者之间的最大值,也就是说 i 的最小值其实是127
                i = Math.max(i, 127);
                // 数组的最大值为 Integer.MAX_VALUE
                // 取 i 和 Integer.MAX_VALUE - (-low) -1 两者之间最小值
                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;

        // 创建大小为 (high - low) + 1 的整型数组
        cache = new Integer[(high - low) + 1];
        int j = low;
        // 创建并缓存 Integer 对象
        for(int k = 0; k < cache.length; k++)
            cache[k] = new Integer(j++);

        // range [-128, 127] must be interned (JLS7 5.1.7)
        // 断言缓存最大值 high 大于等于 127
        assert IntegerCache.high >= 127;
    }

    private IntegerCache() {}
}

结合上面已经分析的 ByteCacheShortCache ,可以看出 IntegerCache 缓存类是有明显区别的,实现代码更多,从 IntegerCache 的定义可以看出,3个成员变量都是静态常量,只有一个静态代码块,没有任何其他的方法,关于静态代码块的分析已经在代码中备注了,从静态代码块中可以得出以下的几点:

  • IntegerCache 的缓存最小值 low 为 -128,与其他的类 ByteShortLong 是相同的;
  • IntegerCache 的缓存最大值 high 可以通过属性 java.lang.Integer.IntegerCache.high 进行设置,而其他的类 ByteShortLong 都是无法设置的;
  • IntegerCache 的缓存最大值 high 的最小值为127,如果通过属性设置的值比127小,则赋值为 127,也就是此时的属性值是无效的;

上面提到 IntegerCache 的缓存最大值 high 可以通过属性 java.lang.Integer.IntegerCache.high 进行设置,那么又该如何配置呢?这里以 Intellij IDEA 进行举例说明,首先找到测试类的配置,然后在 VM Options 参数一栏添加 -Djava.lang.Integer.IntegerCache.high=200 即可,如下:

Byte、Short、Integer、Long内部缓存类的对比与源码分析_缓存数组

此处将 java.lang.Integer.IntegerCache.high 设置为 200,再次运行下面的程序

public static void main(String[] args) {
    Integer num1 = 100;
    Integer num2 = 100;
    System.out.println("num1 与 num2 是否相等:" + (num1 == num2));

    Integer num3 = 128;
    Integer num4 = 128;
    System.out.println("num3 与 num4 是否相等:" + (num3 == num4));
}

输出的结果如下:

num1 与 num2 是否相等:true
num3 与 num4 是否相等:true

重要提示:

在上面的示例代码中,都是直接使用 == 进行比较,主要是为了验证对象是否为同一个,但是在实际的应用中,可能需要比较的是数值是否一样,这个时候如果直接使用 == 比较,可能会出现预期之外的值,具体原因上面也分析过,所以实际开发过程中,不建议直接使用 == 对包装类进行比较,而是使用 equals 进行比较

5、Float 与 Double

分析到此处,可能有人会问,基本的数据类型中还有 floatdouble 类型,它们也有包装类 FloatDouble ,是不是它们也有内部缓存类呢?答案是否定的, FloatDouble 类中是没有内部缓存类的,也就是说不会存在对象复用,比如下面的代码:

public static void main(String[] args) {
    Float num1 = 1F;
    Float num2 = 1F;

    System.out.println(num1 == num2);

    Double num3 = 1D;
    Double num4 = 1D;

    System.out.println(num3 == num4);
}

运行程序,输出结果如下:

false
false
  • 2
  • 1收藏
  • 评论
  • 分享
  • 举报

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK