Java 封装类内部缓存
在 Java 中通过 new 关键字等其他方式在堆(heap)上创建对象是一个比较耗费资源的操作。 Java 在封装类(Wrapper classes)上使用内部缓存来使封装类的某些常用值使用更高效。
与 String 类一样,Java 中的封装类都是不可变的(immutable)。Java 中的封装类与 String 类一样同样提供了类似的缓存池机制。这其实也是一个普遍的操作,对于较昂贵的资源创建操作,使用缓存来处理在其他地方也非常常见,如数据库连接池、线程池等。
Integer 内部缓存
在 Integer 类中存在一个内部类 IntegerCache,使用一个 Integer[] 数组来维持一组 Integer 实例,当我们使用以下语句时:
1 | Integer i = 10; // 或者 |
i 将存储一个指向已经缓存的 Integer 实例的引用。如果使用 new Integer(10),则会在堆上创建一个新的实例,而不是使用已缓存的实例。实际上,这一机制只有在使用 Integer.valueOf() 才起作用,本质上使用字面量的赋值最终也是通过调用 Integer.valueOf() 来实现的。在 Java 执行自动装箱(autoboxing)也是类似的。
IntegerCache 类非常简单,我们可以看一下(jdk 1.8):
1 | /** |
Integer.valueOf() 方法:
1 | /** |
可以看到,IntegerCache 是一个静态内部类,因此,只有我们调用 Integer.valueOf() 方法时,该类才会加载,然后创建缓存。以便提高资源重用率。
下面是使用 Integer 内部缓存的一个简单例子:
1 | public class IntegerCacheDemo { |
1 | # Output: |
当程序执行第一条赋值语句时,IntegerCache 会被加载,并创建相应的缓存池。之后 i2 的赋值使用的已缓存的实例。i3 使用 new 关键字来创建实例,因此,它会在堆上创建一个新的实例对象。== 对于作用于对象之间,比较的是对象的地址,因此,i1 == i2 输出 true, 而 i1 == i3 输出 false。
修改缓存大小
从上面的 IntegerCache 类的实现来看,我们可以通过需求自己更改实例缓存范围的最大值,修改方式如下:
1 | 1. --XX:AutoBoxCache= 1000 |
通过上面的修改后,Integer 的缓存范围将变成 -128 ~ 1000。需要注意的是,当前的实现中,并没有指定 IntegerCache.low 的取值。
其他封装类
通过查看 Integer 类的中 IntegerCache 的相关实现,我们了解的 Integer 的实例缓存方式。更多地,其他的封装类也有提供类似的机制:
java.lang.Boolean默认存储两个实例 ——TRUE和FALSE;java.lang.Short默认缓存范围为-128 ~ 127;java.lang.Byte默认缓存范围-128 ~ 127;java.lang.Long默认缓存范围为-128 ~ 127;java.lang.Character默认缓存范围为0 ~ 127;
需要说明的是,除了本文提到的 Integer 以外,其他的封装类只能使用默认的缓存范围,无法进行更改。Float 和 Double 并没有提供这样的缓存机制。不同于这些封装类,String 使用的是字符串池(string pool),采用不同的实现的方式。
String 会为 String 字面量(literals)维持一个字符串池,该字符串池初始化为空,由 String 类维护,当字符串调用 intern 方法时,会将该字符串与字符串池中串使用 equals 方法进行比较,若返回 true 则直接使用字符串池中的串,返回该串的引用。若字符串池中不存在 equals 的串,则创建相应的串并添加到字符串池中。
1 | /** |
更多详细相关内容见 String literals。
references:
https://docs.oracle.com/javase/specs/jls/se8/html/index.html