Guava-CacheLoader

CacheBulider

适用性

缓存在很多场景下都是相当有用的。例如,计算或检索一个值的代价很高,并且对同样的输入需要不止一次获取值的时候,就应当考虑使用缓存。 缓存在很多场景下都是相当有用的。例如,计算或检索一个值的代价很高,并且对同样的输入需要不止一次获取值的时候,就应当考虑使用缓存。

Guava Cache与ConcurrentMap很相似,但也不完全一样。最基本的区别是ConcurrentMap会一直保存所有添加的元素,直到显式地移除。相对地,Guava Cache为了限制内存占用,通常都设定为自动回收元素。在某些场景下,尽管LoadingCache 不回收元素,它也是很有用的,因为它会自动加载缓存。

通常来说,Guava Cache适用于:

  • 你愿意消耗一些内存空间来提升速度。
  • 你预料到某些键会被查询一次以上。
  • 缓存中存放的数据总量不会超出内存容量。(Guava Cache是单个应用运行时的本地缓存。它不把数据存放到文件或外部服务器。如果这不符合你的需求,请尝试Memcached这类工具)

如果你的场景符合上述的每一条,Guava Cache就适合你。

如同范例代码展示的一样,Cache实例通过CacheBuilder生成器模式获取,但是自定义你的缓存才是最有趣的部分。

:如果你不需要Cache中的特性,使用ConcurrentHashMap有更好的内存效率——但Cache的大多数特性都很难基于旧有的ConcurrentMap复制,甚至根本不可能做到。

1. 基础创建CacheBuilder

    public void test2() {
        // 新建CacheBuilder
        Cache<Integer, String> cache = CacheBuilder.newBuilder().build();
        cache.put(1, "a");
        cache.put(2, "b");
        // 输出: a
        System.out.println(cache.getIfPresent(1));
        // 输出: null
        System.out.println(cache.getIfPresent(3));
        // 输出: {1=a, 2=b}
        System.out.println(cache.getAllPresent(ImmutableList.of(1,2,3)));
    }

2. 无缓存时,自定义缓存值

    public void test3() {
        // 遇到不存在的key,定义默认缓存值
        CacheLoader cacheLoader = new CacheLoader<Integer,String>() {
            @Override
            public String load(Integer key) throws Exception {
                return "缓存中找不到 key[" + key + "]对应的值";
            }
        };

        // 1. 在cache定义时设置通用缓存模版
        LoadingCache<Integer, String> cache1 = CacheBuilder.newBuilder().build(cacheLoader);
        cache1.put(1, "a");
        // 输出: a
        System.out.println(cache1.getIfPresent(1));
        try {
            // 输出: {1=a, 2=缓存中找不到 key[2]对应的值}
            System.out.println(cache1.getAll(ImmutableList.of(1,2)));
            // 输出: 缓存中找不到 key[3]对应的值
            System.out.println(cache1.get(3));
        } catch (ExecutionException e) {
            e.printStackTrace();
        }

         // 2. 在获取缓存值时设置缓存
         Cache<Integer, String> cache2 = CacheBuilder.newBuilder().build();
         cache2.put(1, "a");
        // 输出: a
         System.out.println(cache2.getIfPresent(1));
         try {
             // 输出: key2
             String value = cache2.get(2, () -> "key2");
             System.out.println(value);
         } catch (ExecutionException e) {
             e.printStackTrace();
         }
    }

3. 控制缓存大小

    public void test4() {
        // 1. 基于缓存多少
        Cache<Integer, String> cache1 = CacheBuilder.newBuilder()
                .maximumSize(2L)  // 设置缓存上限,最多两个
                .build();
        cache1.put(1, "a");
        cache1.put(2, "b");
        cache1.put(3, "c");
        // 输出: {3=c, 2=b}
        System.out.println(cache1.asMap());
        // 输出: b
        System.out.println(cache1.getIfPresent(2));
        cache1.put(4, "d");
        // 由于key 2被调用获取过,所以key 3会被回收 输出: {2=b, 4=d}
        System.out.println(cache1.asMap());
    }

4. 控制缓存回收的时间

   public void test5() {
        // 1. 设置缓存写入后多久过期
        Cache<Integer, Integer> cache1 = CacheBuilder.newBuilder()
                // 缓存写入2s后过期
                .expireAfterWrite(2, TimeUnit.SECONDS)
                .build();
        cache1.put(1,1);
        // 输出: {1=1}
        System.out.println(cache1.asMap());
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        // 输出: {}
        System.out.println(cache1.asMap());

        // 2. 设置缓存在给定时间内没有被读/写访问后过期
        Cache<Integer, Integer> cache2 = CacheBuilder.newBuilder()
                // 缓存读取2s后过期
                .expireAfterAccess(2, TimeUnit.SECONDS)
                .build();
        cache2.put(1,1);
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        cache2.getIfPresent(1);
        // 输出: {}
        System.out.println(cache2.asMap());
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        // 输出: {}
        System.out.println(cache2.asMap());
    }

5. 手动清除缓存

    public void test6() {
        // 清除缓存中的数据
        Cache<Integer, String> cache = CacheBuilder.newBuilder().build();
        cache.put(1, "a");
        System.out.println(cache.asMap());  // 输出: {1=a}
        cache.invalidateAll();  // 清除所有缓存
        System.out.println(cache.asMap());  // 输出: {}
        cache.put(2, "b");
        System.out.println(cache.asMap());  // 输出: {2=b}
        cache.invalidate(2);  // 清除指定缓存
        System.out.println(cache.asMap());  // 输出: {}
        cache.put(1, "a");
        cache.put(2, "b");
        cache.put(3, "c");
        System.out.println(cache.asMap());  // 输出: {2=b, 1=a, 3=c}
        cache.invalidateAll(ImmutableList.of(1,2));  // 批量清除缓存
        System.out.println(cache.asMap());  // 输出: {3=c}
    }

6. 设置监听器

    public void test7() {
        // 设置移除监听器
        LoadingCache<Integer, Integer> cache = CacheBuilder.newBuilder()
                .expireAfterWrite(2, TimeUnit.SECONDS)  // 设置2s后过期时间
                .removalListener(notification -> System.out.println(
                        "移除key[" + notification.getKey()
                                + "],value[" + notification.getValue()
                                + "],移除原因[" + notification.getCause() + "]")
                )  // 设置移除监听器,并设置输出模版
                .build(
                        new CacheLoader<Integer, Integer>() {
                            @Override
                            public Integer load(Integer key) throws Exception {
                                return 2;  // 当无值时, 设置默认值
                            }
                        }
                );
        cache.put(1, 1);
        cache.put(2, 2);
        System.out.println(cache.asMap());  // 输出: {2=2, 1=1}
        cache.invalidateAll();
        System.out.println(cache.asMap());  // 输出: {}
        cache.put(3, 3);
        try {
            // 如果定义的CacheLoader没有声明任何检查型异常,则可以通过getUnchecked()取值
            System.out.println(cache.getUnchecked(3));  // 输出: 3
            Thread.sleep(3000);
            System.out.println(cache.getUnchecked(3));  // 输出: 2
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

7. 统计功能

    public void test8() {
        /**
         * hitCount;  // 缓存命中数
         * missCount; // 缓存未命中数
         * loadSuccessCount; // load成功数
         * loadExceptionCount; // load异常数
         * totalLoadTime; // load的总共耗时
         * evictionCount; // 缓存项被回收的总数,不包括显式清除
         */
        // 开启统计,并查看统计信息
        LoadingCache<String, String> cache = CacheBuilder.newBuilder()
                .recordStats()  // 开启统计功能
                .refreshAfterWrite(2, TimeUnit.SECONDS)  // 缓存写入2s后更新
                .build(new CacheLoader<String, String>() {
                    @Override
                    public String load(String key) throws Exception {
                        return UUID.randomUUID().toString();
                    }
                });
        cache.put("1", "a");
        System.out.println(cache.asMap());  // 输出: {1=a}
        System.out.println(cache.stats());  // 输出: CacheStats{hitCount=0, missCount=0, loadSuccessCount=0, loadExceptionCount=0, totalLoadTime=0, evictionCount=0}
        cache.getIfPresent("2");
        System.out.println(cache.asMap());  // 输出: {1=a}
        System.out.println(cache.stats());  // 输出: CacheStats{hitCount=0, missCount=1, loadSuccessCount=0, loadExceptionCount=0, totalLoadTime=0, evictionCount=0}
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //此处缓存里的值都是过期了的
        cache.getIfPresent("1");
        System.out.println(cache.asMap());  // 输出: {1=0207bb01-7b3c-4b66-b575-9fb2c5511a96}
        System.out.println(cache.stats());  // 输出: CacheStats{hitCount=1, missCount=1, loadSuccessCount=1, loadExceptionCount=0, totalLoadTime=21118733, evictionCount=0}
    }
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 219,110评论 6 508
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,443评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 165,474评论 0 356
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,881评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,902评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,698评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,418评论 3 419
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,332评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,796评论 1 316
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,968评论 3 337
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,110评论 1 351
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,792评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,455评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,003评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,130评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,348评论 3 373
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,047评论 2 355

推荐阅读更多精彩内容

  • 使用场景 缓存在很多场景下都是相当有用的。例如,计算或检索一个值的代价很高,并且对同样的输入需要不止一次获取值的时...
    jiangmo阅读 797评论 0 3
  • 缓存 范例 适用性 缓存在很多场景下都是相当有用的。例如,计算或检索一个值的代价很高,并且对同样的输入需要不止一次...
    小鸡在路上阅读 3,240评论 0 2
  • 范例 适用性 缓存在很多场景下都是相当有用的。例如,计算或检索一个值的代价很高,并且对同样的输入需要不止一次获取值...
    爱情小傻蛋阅读 653评论 0 2
  • 缓存在日常开发中举足轻重,如果你的应用对某类数据有着较高的读取频次,并且改动较小时那就非常适合利用缓存来提高性能。...
    tracy_668阅读 4,309评论 0 9
  • 原文链接:原文链接 注:这篇文章是我自己根据官方文档的原文翻译的,因为能力有限,有些地方翻译的不好,欢迎批评指正,...
    大风过岗阅读 27,059评论 0 16