当前位置 : 主页 > 编程语言 > java >

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

来源:互联网 收集:自由互联 发布时间:2022-08-10
这是《水煮 JDK 源码》系列 的第7篇文章,计划撰写100篇关于JDK源码相关的文章 对于基本数据类型的包装类 Byte、Short、Integer、Long ,其内部实现都有一个缓存类,这个缓存类主要用于缓

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

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

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

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

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

那么对于 Byte、Short、Long 是否也是一样的呢?答案是否定的,也就是说,如果上面程序的类型改成 Byte、Short、Long 任意一种,其输出结果只会有一种,也就是对应 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)); } }

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

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

那么如果换一种方式定义 byte1、byte2、byte3,结果又会怎么样呢?

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() 并不一样,所以 创建的 byte1、byte2、byte3 对象并不相同。

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 的实现和 ShortCache 、ByteCache 并无区别,只是类型不一样而已,下面来看看其 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() {} }

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

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

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

::: hljs-center

image20220804102849895.png

:::

此处将 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

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

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
网友评论