String.intern() 和动态常量池

内容纲要

背景

先看图

上面是从数据库里查询出的应用信息,这样的数据结构在我们的程序里大量存在:除了主键以外,其他字段,尤其是外键,存在大量的重复值。

以上图的应用信息为例,可以看到类别字段的值就存在重复,如果从数据库里查询出当前在投广告的应用详情并缓存起来,随着应用信息的增加,会浪费大量的内存

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() 和动态常量池

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注

Scroll to top
粤ICP备2020114259号 粤公网安备44030402004258