内容纲要
背景
先看图
上面是从数据库里查询出的应用信息,这样的数据结构在我们的程序里大量存在:除了主键以外,其他字段,尤其是外键,存在大量的重复值。
以上图的应用信息为例,可以看到类别字段的值就存在重复,如果从数据库里查询出当前在投广告的应用详情并缓存起来,随着应用信息的增加,会浪费大量的内存
String.intern()
关于 String.intern() 就不赘述了,很适合这种需要缓存大量重复值的场景,对于上面的应用信息,可以如下优化内存的占用
public class App {
private Long id;
private String name;
private String category;
public App(Long id, String name, String category) {
this.id = id;
this.name = name;
this.category = category.intern();
}
......
}
动态常量池
一方面,仅 String 提供了 intern
这样的方法,其他类型并没有获得原生的常量池支持;但是,即使是 int,long 这样的原始类型,如果要用作缓存 key 的话,也是要包装成对象的,就如上图里的应用信息一样,其 id 虽然并不会重复,但却是很多表的 key,这些表如果要加载入缓存,就同样存在重复的 id 的情况
另一方面,对于原生的字符串常量池,并无法有效的控制,比如想要清理常量池里的常量;或者对于常量池的大小进行限制
这样就有必要设计一个通用的常量池了。对此,guava 里倒是有一个现成的实现:Interners
;当然,guava 的这个实现也有点粗糙,可以利用 guava 的 LoadingCache
,实现一个可控的常量池
public final class ConstantPool {
private ConstantPool() {
}
private static final LoadingCache<String, String> STRING_CACHE = CacheBuilder.newBuilder().initialCapacity(512)
.expireAfterAccess(24, TimeUnit.HOURS).build(new CacheLoader<String, String>() {
@Override
public String load(String key) {
return key;
}
});
private static final LoadingCache<Long, Long> LONG_CACHE = CacheBuilder.newBuilder().initialCapacity(512)
.expireAfterAccess(24, TimeUnit.HOURS).build(new CacheLoader<Long, Long>() {
@Override
public Long load(Long key) {
return key;
}
});
private static final LoadingCache<Integer, Integer> INTEGER_CACHE = CacheBuilder.newBuilder().initialCapacity(512)
.expireAfterAccess(24, TimeUnit.HOURS).build(new CacheLoader<Integer, Integer>() {
@Override
public Integer load(Integer key) {
return key;
}
});
public static final Integer cint(Integer key) {
return key == null ? null : INTEGER_CACHE.getUnchecked(key);
}
public static final Long clong(Long key) {
return key == null ? null : LONG_CACHE.getUnchecked(key);
}
public static final String cstr(String key) {
return key == null ? null : STRING_CACHE.getUnchecked(key);
}
}
总结
什么情况用到动态常量池呢?
- 需要缓存对象的场景,这些被缓存的对象可能是从数据库里查询得来的,或者是从一些第三方接口获取的
- 被缓存的对象,某些属性有大量的重复值;例如,上面例子里的应用分类字段
- 或者,某个属性在多个被缓存的对象里存在;例如,应用 Id 本身几乎不会重复,但是有多个场合都需要用应用 Id 作为缓存的 Key来映射不同的对象值
String.intern() 和动态常量池