guava cache 简单入门

最简单的例子

public class GuavaCacheTest {
    public static void main(String[] args) {
        Cache<Integer, String> cache = CacheBuilder.newBuilder().build();
        cache.put(1, "a");
        System.out.println(cache.getIfPresent(1));
        System.out.println(cache.getIfPresent(2));
    }
}

执行结果

若已缓存过,返回缓存的值,否则获取不到值。

如果没有缓存,我也想要一个缓存值

有两种方法可以实现"如果有缓存则返回;否则运算、缓存、然后返回"。

CacheLoader

Callable:所有类型的Guava Cache,不管有没有自动加载功能,都支持get(K, Callable<V>方法。

public class GuavaCacheTest {
    public static void main(String[] args) {
        LoadingCache<Integer, String> cache = CacheBuilder.newBuilder().build(
                new CacheLoader<Integer, String>() {
                    @Override
                    public String load(Integer key) throws Exception {
                        return "key-" + key;
                    }
                }
        );
        cache.put(1, "a");
        System.out.println(cache.getIfPresent(1));
        try {
            System.out.println(cache.get(2));
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }
}

public class GuavaCacheTest {
    public static void main(String[] args) {
        Cache<Integer, String> cache = CacheBuilder.newBuilder().build();
        cache.put(1, "a");
        System.out.println(cache.getIfPresent(1));
        try {
            String value = cache.get(2, new Callable<String>() {
                @Override
                public String call() throws Exception {
                    return "hello,world";
                }
            });
            System.out.println(value);
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }
}

内存一直被占用?可怕:缓存回收

上面的简单例子中,内存一直会被占用,这是不合理的,我们需要按照一定的规则将缓存清除,释放内存,防止发生OOM。

guava提供了三种缓存回收的策略:基于容量回收、定时回收和基于引用回收。

基于容量的回收:

规定缓存项的数目不超过固定值,只需使用CacheBuilder.maximumSize(long)。缓存将尝试回收最近没有使用或总体上很少使用的缓存项。——警告:在缓存项的数目达到限定值之前,即缓存项的数目逼近限定值时缓存就可能进行回收操作。这个size指的是cache中的条目数,不是内存大小或是其他.

public class GuavaCacheTest {
    public static void main(String[] args) {
        Cache<Integer, String> cache = CacheBuilder.newBuilder().maximumSize(2).build();
        cache.put(1, "a");
        cache.put(2, "b");
        cache.put(3, "c");
        System.out.println(cache.asMap());
        System.out.println(cache.getIfPresent(2));
        cache.put(4, "d");
        System.out.println(cache.asMap());
    }
}

基于容量的缓存回收还可以指定缓存项的权重,使用CacheBuilder.weigher(Weigher)指定一个权重函数,并且用CacheBuilder.maximumWeight(long)指定最大总重。

缓存回收也是在重量逼近限定值时就进行了,还要知道重量是在缓存创建时计算的,因此要考虑重量计算的复杂度。

public class GuavaCacheTest {
    public static void main(String[] args) {
        Cache<Integer, Integer> cache = CacheBuilder.newBuilder().maximumWeight(100)
                .weigher(new Weigher<Integer, Integer>() {
                    @Override
                    public int weigh(Integer key, Integer value) {
                        if (value % 2 == 0) {
                            return 20;
                        } else {
                            return 5;
                        }
                    }
                }).build();
//         放偶数
        for (int i = 0; i <= 20; i += 2) {
            cache.put(i, i);
        }
        System.out.println(cache.asMap());
        cache.invalidateAll();
        for (int i = 1; i < 10; i += 1) {
            cache.put(i, i);
        }
        System.out.println(cache.asMap());
    }
}

CacheBuilder.maximumSize(long),CacheBuilder.maximumWeight(long)是互斥的,只能二选一。

CacheBuilder.maximumSize(long)中如果每个条目占用内存空间都是相同的,就等价于限制了缓存空间的总大小;如果每个缓存条目大小不定,那么就没有办法限制总的内存大小。

CacheBuilder.maximumWeight(long)可以用来控制内存。比如我们将总权重设置为1G(代表内存空间大小),而每个缓存条目的权重都是缓存值实际占用的内存空间大小

基于时间的回收

guava 提供两种定时回收的方法

expireAfterAccess(long, TimeUnit):缓存项在给定时间内没有被读/写访问,则回收。请注意这种缓存的回收顺序和基于大小回收一样。

expireAfterWrite(long, TimeUnit):缓存项在给定时间内没有被写访问(创建或覆盖),则回收。如果认为缓存数据总是在固定时候后变得陈旧不可用,这种回收方式是可取的。

public class GuavaCacheTest {
    public static void main(String[] args) {
        Cache<Integer, Integer> cache = CacheBuilder.newBuilder().expireAfterWrite(3, TimeUnit.SECONDS).build();
        cache.put(1,1);
        System.out.println(cache.asMap());
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(cache.asMap());
    }
}
public class GuavaCacheTest {
    public static void main(String[] args) {
        Cache<Integer, Integer> cache = CacheBuilder.newBuilder().expireAfterAccess(3, TimeUnit.SECONDS).build();
        cache.put(1,1);
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        cache.getIfPresent(1);
        System.out.println(cache.asMap());
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(cache.asMap());
    }
}

基于时间的缓存回收可以和基于容量的缓存回收一起使用,这样可以避免:当缓存创建速度,远远大于过期速度的时候出现OOM的问题。

Cache<Integer, Integer> cache = CacheBuilder.newBuilder().maximumSize(100).expireAfterAccess(3, TimeUnit.SECONDS).build();

基于引用的回收

手动清除缓存

任何时候,你都可以显式地清除缓存项,而不是等到它被回收:

个别清除:Cache.invalidate(key)

批量清除:Cache.invalidateAll(keys)

清除所有缓存项:Cache.invalidateAll()

public class GuavaCacheTest {
    public static void main(String[] args) {
        Cache<Integer, Integer> cache = CacheBuilder.newBuilder().maximumSize(100).expireAfterAccess(3, TimeUnit.SECONDS).build();
        cache.put(1, 1);
        cache.put(2, 2);
        cache.invalidateAll(Lists.newArrayList(1));
        System.out.println(cache.asMap());
        cache.put(3, 3);
        System.out.println(cache.asMap());
        cache.invalidateAll();
        System.out.println(cache.asMap());
    }
}

监听器

public class GuavaCacheTest {
    public static void main(String[] args) {
        LoadingCache<Integer, Integer> cache = CacheBuilder.newBuilder().expireAfterWrite(3, TimeUnit.SECONDS).removalListener(new RemovalListener<Object, Object>() {
            @Override
            public void onRemoval(RemovalNotification<Object, Object> notification) {
                System.out.println("remove key[" + notification.getKey() + "],value[" + notification.getValue() + "],remove reason[" + notification.getCause() + "]");
            }
        }).recordStats().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());
        cache.invalidateAll();
        System.out.println(cache.asMap());
        cache.put(3, 3);
        try {
            System.out.println(cache.getUnchecked(3));
            Thread.sleep(4000);
            System.out.println(cache.getUnchecked(3));
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

刷新

public class GuavaCacheTest {
    public static void main(String[] args) {
        LoadingCache<Integer, Integer> cache = CacheBuilder.newBuilder().expireAfterWrite(3, TimeUnit.SECONDS).removalListener(new RemovalListener<Object, Object>() {
            @Override
            public void onRemoval(RemovalNotification<Object, Object> notification) {
                System.out.println("remove key[" + notification.getKey() + "],value[" + notification.getValue() + "],remove reason[" + notification.getCause() + "]");
            }
        }).recordStats().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());
        cache.refresh(1);
        System.out.println(cache.asMap());
    }
}
  • 刷新表示为键加载新值,这个过程可以是异步的。在刷新操作进行时,缓存仍然可以向其他线程返回旧值.

  • 而不像回收操作,读缓存的线程必须等待新值加载完成。

  • 如果刷新过程抛出异常,缓存将保留旧值,

统计

CacheBuilder.recordStats()用来开启Guava Cache的统计功能。统计打开后,Cache.stats()方法会返回对象以提供如下统计信息:
hitRate():缓存命中率;
averageLoadPenalty():加载新值的平均时间,单位为纳秒;
evictionCount():缓存项被回收的总数,不包括显式清除
统计信息对于调整缓存设置是至关重要的,在性能要求高的应用中我们建议密切关注这些数据。

其他

asMap()方法获得缓存数据的ConcurrentMap<K, V>快照

cleanUp()清空缓存

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,001评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,210评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,874评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,001评论 1 291
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,022评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,005评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,929评论 3 416
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,742评论 0 271
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,193评论 1 309
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,427评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,583评论 1 346
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,305评论 5 342
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,911评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,564评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,731评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,581评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,478评论 2 352

推荐阅读更多精彩内容

  • com.google.common.cache 1、背景 缓存,在我们日常开发中是必不可少的一种解决性能问题的方法...
    拾壹北阅读 22,290评论 0 25
  • Google Guava Cache是一种非常优秀本地缓存解决方案,提供了基于容量,时间和引用的缓存回收方式。基于...
    Acamy丶阅读 25,882评论 3 34
  • 鼓励 小宝刚刚跑过来兴奋的说:“妈妈,小鱼还没死,还活着呢”, 前两天她去超市买了两条小鱼,买的时候我就和她说:小...
    周丽霞_8061阅读 146评论 0 0
  • 这几天一直在忙于春节前的客户送礼品。在坡上一年的主要节日是春节、中秋节、圣诞节、新年。圣诞节和新年离得很近,在送礼...
    韦陀阅读 354评论 0 0