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