此文已由作者赵计刚授权网易云社区发布。
欢迎访问网易云社区,了解更多网易技术产品运营经验。
1、guava cache
2、使用实例
具体在实际中使用的例子,去查看《第七章 企业项目开发--本地缓存guava cache》,下面只列出测试实例:
import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; import com.google.common.cache.CacheBuilder; import com.google.common.cache.CacheLoader; import com.google.common.cache.LoadingCache; public class Hello{ LoadingCache<String, String> testCache = CacheBuilder.newBuilder() .expireAfterWrite(20, TimeUnit.MINUTES)// 缓存20分钟 .maximumSize(1000)// 最多缓存1000个对象 .build(new CacheLoader<String, String>() { public String load(String key) throws Exception { if(key.equals("hi")){ return null; } return key+"-world"; } }); public static void main(String[] args){ Hello hello = new Hello(); System.out.println(hello.testCache.getIfPresent("hello"));//null hello.testCache.put("123", "nana");//存放缓存 System.out.println(hello.testCache.getIfPresent("123"));//nana try { System.out.println(hello.testCache.get("hello"));//hello-world } catch (ExecutionException e) { e.printStackTrace(); } System.out.println(hello.testCache.getIfPresent("hello"));//hello-world /***********测试null*************/ System.out.println(hello.testCache.getIfPresent("hi"));//null try { System.out.println(hello.testCache.get("hi"));//抛异常 } catch (ExecutionException e) { e.printStackTrace(); } } }
在这个方法中,基本已经覆盖了guava cache常用的部分。
3、源代码
在阅读源代码之前,强烈建议,先看一下"Java并发包类源码解析"中的《第二章 ConcurrentHashMap源码解析》,链接如下:
http://www.cnblogs.com/java-zhao/p/5113317.html
对于源码部分,由于整个代码的核心类LocalCache有5000多行,所以只介绍上边给出的实例部分的相关源码解析。本节只说一下缓存器的构建,即如下代码部分:
LoadingCache<String, String> testCache = CacheBuilder.newBuilder() .expireAfterWrite(20, TimeUnit.MINUTES)// 缓存20分钟(时间起点:entry的创建或替换(即修改)) //.expireAfterAccess(10, TimeUnit.MINUTES)//缓存10分钟(时间起点:entry的创建或替换(即修改)或最后一次访问) .maximumSize(1000)// 最多缓存1000个对象 .build(new CacheLoader<String, String>() { public String load(String key) throws Exception { if(key.equals("hi")){ return null; } return key+"-world"; } });
说明:该代码的load()方法会在之后将get(Object key)的时候再说,这里先不说了。
对于这一块儿,由于guava cache这一块儿的代码虽然不难,但是容易看的跑偏,一会儿就不知道跑到哪里去了,所以我下边先给出guava cache的数据结构以及上述代码的执行流程,然后大家带着这个数据结构和执行流程去分析下边的源代码,分析完源代码之后,我在最后还会再将cache的数据结构和构建缓存器的执行流程给出,并会结合我们给出的开头实例代码来套一下整个流程,最后画出初始化构建出来的缓存器(其实,这个缓存器就是上边以及文末给出的cache的数据结构图)。
guava cache的数据结构图:
需要说明的是:
后边三条与ConcurrentHashMap一样
guava cache的数据结构的构建流程:
1)构建CacheBuilder实例cacheBuilder
2)cacheBuilder实例指定缓存器LocalCache的初始化参数
3)cacheBuilder实例使用build()方法创建LocalCache实例(简单说成这样,实际上复杂一些)
3.1)首先为各个类变量赋值(通过第二步中cacheBuilder指定的初始化参数以及原本就定义好的一堆常量)
3.2)之后创建Segment数组
3.3)最后初始化每一个Segment[i]
3.3.1)为Segment属性赋值
3.3.2)初始化Segment中的table,即一个ReferenceEntry数组(每一个key-value就是一个ReferenceEntry)
3.3.3)根据之前类变量的赋值情况,创建相应队列,用于LRU缓存回收算法
类结构:(这个不看也罢)
关于上边的这些说明,结合之后的源码进行看就好了。
注:如果在源码中有一些注释与最后的套例子的注释不同的话,以后者为准
3.1、构建CacheBuilder+为LocalCache设置相关参数+创建LocalCache实例
CacheBuilder的一些属性:
private static final int DEFAULT_INITIAL_CAPACITY = 16;//用于计算每个Segment中的hashtable的大小 private static final int DEFAULT_CONCURRENCY_LEVEL = 4;//用于计算有几个Segment private static final int DEFAULT_EXPIRATION_NANOS = 0;//默认的缓存过期时间 static final int UNSET_INT = -1; int initialCapacity = UNSET_INT;//用于计算每个Segment中的hashtable的大小 int concurrencyLevel = UNSET_INT;//用于计算有几个Segment long maximumSize = UNSET_INT;//cache中最多能存放的缓存entry个数 long maximumWeight = UNSET_INT; Strength keyStrength;//键的引用类型(strong、weak、soft) Strength valueStrength;//值的引用类型(strong、weak、soft) long expireAfterWriteNanos = UNSET_INT;//缓存超时时间(起点:缓存被创建或被修改) long expireAfterAccessNanos = UNSET_INT;//缓存超时时间(起点:缓存被创建或被修改或被访问)
CacheBuilder-->newCacheBuilder():创建一个CacheBuilder实例
接下来,使用构建器模式指定一些属性值(这里的话,就是超时时间:expireAfterWriteNanos+cache中最多能放置的entry个数:maximumSize),这里的entry指的就是一个缓存(key-value对)
CacheBuilder-->expireAfterWrite(long duration, TimeUnit unit)
/** * 指明每一个entry(key-value)在缓存中的过期时间 * 1、时间的参考起点:entry的创建或值的修改 * 2、过期的entry也许会被计入缓存个数size(也就是说缓存个数不仅仅只有存活的entry) * 3、但是过期的entry永远不会被读写 */ public CacheBuilder<K, V> expireAfterWrite(long duration, TimeUnit unit) { /* * 检查之前是否已经设置过缓存超时时间 */ checkState(expireAfterWriteNanos == UNSET_INT,//正确条件:之前没有设置过缓存超时时间 "expireAfterWrite was already set to %s ns",//不符合正确条件的错误信息 expireAfterWriteNanos); /* * 检查设置的超时时间是否大于等于0,当然,通常情况下,我们不会设置缓存为0 */ checkArgument(duration >= 0, //正确条件 "duration cannot be negative: %s %s",//不符合正确条件的错误信息,下边的是错误信息中的错误参数 duration, unit); this.expireAfterWriteNanos = unit.toNanos(duration);//根据输入的时间值与时间单位,将时间值转换为纳秒 return this; }
注意:
免费领取验证码、内容安全、短信发送、直播点播体验包及云服务器等套餐
更多网易技术、产品、运营经验分享请点击。