Guava Cache官方文档

原文链接:原文链接

注:这篇文章是我自己根据官方文档的原文翻译的,因为能力有限,有些地方翻译的不好,欢迎批评指正,欢迎拍砖!

一、缓存Caches


例子:


案例

1.适用性

缓存在很多数情况下都非常有用,例如,如果计算一个值或者获取一个值时,代价十分昂贵的话,你就可以考虑使用缓存,并且针对某个特定的输入,你可能不止一次需要它的值。

缓存Cache和ConcurrentMap很相像,但又不完全一样。最根本的区别是,ConcurrentMap会保存所有添加到其中的元素直到它们被明确的移除。在另一方面,Cache通常可以配置一个自动化的回收策略去限制它的内存空间。在某些情况下,LoadingCache还是非常有用的,即使它不清除条目。

一般来说,Guava Cache的功能在这些情况下,都是有用的:

(1).你打算牺牲更多内存来换取速度的提升。

(2).缓存的数据会频繁的使用到

 (3).Guava Cache只会把数据存储在内存中(Guava  Cache是把数据存储于你运行的单个应用上,它不会吧数据存储在文件或外部的服务器上,如果这满足不了你的需求,你可以考虑使用Memcached)

如果上面的这些条件都能比较符合你的要求的话,那么Guava Cache对你来说将是一个完美的选择。正如上面的案例代码中所描述的,你可以使用CacheBuilder来获取一个Cache缓存,但是自定义你自己的缓存才是最有趣的部分。

 注意:  如果你不需要上述的这些缓存特性的话,ConcurrentHashMap将会是一个更高效内存缓存选择,但是使用任何老版本的ConcurrentMap都很难复制上面这些缓存特性。


2.population

在你使用Guava Cache时,第一个要问你自己的问题就是: 你有加载或计算一个键所关联的给定值的需求吗?如果有,那么你应该使用CacheLoader。如果没有的话,或许你需要去重写一下默认的实现。但是如果你还是想要原子性"get-if-compute" 语义的话,你可以给get()方法传递一个Callable对象。使用Cache.put()方法可以直接把元素插入到缓存里面,但是我们更偏爱于自动化缓存加载cache loading。因为这让所有的cache缓存的一致性变得更简单。


2.1 CacheLoader

在创建一个LoadingCache时都会附带着一个CacheLoader,创建一个CacheLoader通常都比较简单,只需要实现 V load(K key) throws Exception方法就行了,因此,你可以使用下面的代码来创建一个LoadingCache:


创建loadingCache

查询一个LoadingCache的正确方式就是调用get(k)方法。这个方法要么返回一个已经存在的缓存值,要么利用缓存的CacheLoader去自动地向cache缓存中存入一个新值。 因为CacheLoader可能会抛出一个异常Excetption ,所以LoadingCache.get(K)方法也有可能抛出一个ExecutionException异常(如果cache loader抛出一个unchecked exception,那么get(k)将抛出一个UncheckedExecutionException)

当然,你也可以选择使用getUnchecked(k),这个方法会把所有的异常包裹在UncheckedExecutionException,但是如果内部的CacheLoader抛出一个检查时异常的话,这可能会产生很奇怪的行为。


no checked exception

我们可以调用getAll(Iterable<? extends K>)方法执行一个大批量的查询操作。默认情况下,针对每一个不在缓存中的key,getAll()方法都会单独调用一次CacheLoader.load。当批量获取比发出许多单独的查询更高效时,你可以重写CacheLoader.loadAll()方法,相应地,getAll(Iterable)的执行性能也会相应提升。

请注意: 你可以写一个CacheLoader.loadAll实现来加载那些没被请求的key值。如果你计算了某一个组里面任一key的值,那么loadAll方法就会与此同时把组里面的剩余的其他key值也加载出来。


2.2 Callable

所有的Guava Cache,不管加载与否 都支持get(K,Callable<V>)方法,这个方法会返回和某个key关联的值或者 从Callable里面计算出来值并把它放入缓存cache中。这个方法提供了一种简单的“ if cached,return ;otherwise create, cache and return ” 模式。


使用callable

2.3 直接插入inserted Directly

可以调用cache.put(key,value)方法向缓存中直接插入值,如果给定键的值已经存在于缓存cache中了,那么原来的值将会被覆盖掉。我们也可以使用Cache.asMap()视图暴露的方法对cache做一些改动。请注意,asMap视图中没有方法可以自动地把条目加载到缓存cache中,因此,Cache.get(K,Callable<V>),更进一步地说,在这个asMap视图上原子性操作是在自动cache loading的范围之外操作的,所以在cache中加载值,相比于Cache.asMap().putIfAbsent()方法我们更喜欢Cache.get(K,Callable<V>),因为缓存加载值时,要么使用CacheLoader,要么使用Callable。



3.回收 eviction

最残酷的现实就是我们并没有足够的内存去缓存我们想缓存的一切数据。你必须决定: 在什么时候不应该保存一个缓存条目?

 Guava 提供了三种基本类型的对象回收策略: 基于大小的回收,基于时间的回收,基于引用的回收。


3.1基于大小的回收

如果你的cache缓存不能超过一定大小的话,只需要使用CacheBuilder.maximumSize(long)方法即可。那么缓存cache将会试图回收那些不经常用到的条目。Warning: 缓存会在到达上限之前把某些条目的内存回收掉。

  除此之外,如果不同的条目具有不同权重 --例如,如果你的不同缓存值占用不同的内存空间的话,你可以使用CacheBuilder.weigher(Weigher)方法来指定一个权重,并且可以通过CacheBuilder.maximumWeight(long) 设置最大权重值。在这里补充一个说明,和maximumSize要求的一样,权重值要在条目创建的时候被计算出来的并且在那之后都是不可变的。


权重值

3.2 基于时间的回收

CacheBuilder为基于时间的回收提供了俩个方法:

.expireAfterAccess(long,TimeUnit): 只过期那些持续了指定时间的条目。 注意: 回收条目的顺序和基于大小的回收很相似。

 .expireAfterWrite(long,TimeUnit):  自条目被创建或最近替换了值之后,过了指定时间后,把这些条目过期。如果缓存中的数据在一段时间之                                                            后变的陈旧不可用的话,那么使用这个策略将是十分可取的。


3.3基于引用的回收



3.4 显示清除

在任何时间,除了等待条目被回收之外,你还可以显示地使用清除数据,可以通过下面的方式来显示地清除:

      .单独地使用 Cache.invalidate(key)

      .批量清除,可以使用Cache.invalidateAll(keys)

      .清除所有条目,可以使用  Cache.invalidateAll()



4.清除监听器Removal Listener

当一个条目被清除时候,你可以为你的缓存指定一个清除监听器来执行一些操作。可以通过 CacheBuilder.removalListener(RemovalListener)方法添加一个监听器。 RemovalListener会得到一个RemovalNotification,RemovalNotification中会指定清除原因RemovalCause,键和值。

注意:RemovalListener抛出的任何异常都会被日志记录并且吞下去。


清除监听器

警告:默认情况下,清除监听器的操作都是同步执行的,由于cache的保存操作是在正常的缓存操作期间进行的,所以昂贵的清除监听器就会降低正常的cache缓存的功能。 使用RemovalListeners.asynchronous(RemovalListener, Executor)  可以声明一个异步操作的RemovalListener。


5.什么时候执行清理工作?

使用CacheBuilder创建的Caches 不会自动执行清理和回收工作 相反,它会在在写操作或者读操作期间,只执行小量的维护maintenance操作。

之所以这样做的原因是:

  如果我们想连续地执行cache maintenance 缓存维护工作,那么我们就需要创建一个线程,并且该线程的操作会和用户的操作竞争共享锁。   除此之外,某些环境会严格限制线程的创建(因为在那些环境下,线程的创建会导致CacheBuilder不可用)。

  相反,我们把选择权交给你,如果你的cache缓存吞吐量比较高的话,你没必要为执行缓存维护(cachemaintenance清除过期条目等类似的操作)而担心。

  如果你的cache确实很少执行写操作的话并且你不想因为清理工作而导致缓存读受阻塞,那么你可以创建一个你自己的维护maintenance线程,  这个维护线程会以一定的时间间隔调用Cache.cleanUp()。

如果你想定期为一个写少读多的cache缓存执行维护工作(cache maintenance) ,可以使用ScheduledExecutorService来做这件事。


5.刷新refresh

  刷新和回收不太一样,正如LoadingCache.refresh(K)所说的,刷新一个键key的话,会为这个键加载新的值,这可能是异步的。当键正在被刷新时,会依然把旧值返回给调用者。对比之下,回收会强制要求获取值的操作先等待直到新值被加载进去。

如果在刷新过程中出现了什么异常,那么会依然把旧值保存在cache缓存中,并且会针对这个异常做日志记录同时把异常吞掉。

在刷新时,通过重写CacheLoader.reload(K,V)方法, CacheLoader可以指定一个智能的行为:它允许你在计算新值的过程中,仍然还可以继续使用旧值。


刷新

可以使用CacheBuilder.refreshAfterWrite(long,TimeUnit)设置让缓存自动定时刷新。和expireAfterWrite不同,refreshAfterWrite 会使一个key在给定的时间段之后有资格进行刷新操作。但是只有当一个条目被查询过之后,才会实际地初始化一个刷新操作。(如果CacheLoader.reload是一个异步实现,那么刷新操作不会降低查询速度)。例如,你可以在一个cache上同时设置 refreshAfterWrite和expireAfterWrite。

那么当一个条目具有资格执行刷新操作的时候,该条目上的过期定时器就不会被盲目地重置。因此,如果一个条目在它具有了刷新资格之后没被查询过,那么,就允许对它执行过期操作。



6.特性 Features


6.1 统计 Statistics

通过使用CacheBuilder.recordStats(),你可以让Guava Cache进行统计信息收集工作,Cache.stats()方法会返回一个缓存状态CacheStats对象,这个缓存状态对象里面提供如下信息:

       (1).hitRate(),返回查询的命中率

      (2).averageLoadPenalty(),加载新值时的平均消耗时间

      (3).evictionCount(),缓存回收的数量。

    还有一些其他的统计信息,这些统计信息在缓存cache调优时,十分有用,如果你这个应用比较重视性能的话,我们建议您关注一下这些统计信息。


6.2 asMap

使用缓存cache的视图,你可以把任一个cache缓存看成一个ConcurrentMap,但是asMap视图是如何跟Cache进行交互的,这里我们需要做一些解释:

  (1)cache.asMap()  包含当前加载进Cache缓存中的所有条目,例如,cache.asMap().keySet()包含了所有当前已经加载的键。

  (2)asMap().get(key)  本质上等同于cache.getIfPresent(key)并且不会导致值被加载进内存中,这和Map的约定是一致的。

  (3)所有的cache的读操作和写操作都会导致访问时间被重置。


7.中断interruption

加载方法比如get()永远不会抛出一个InterruptedException,我们本可以把这些方法设计成支持InterruptedException的,但是我们的支持并不完整,导致的结果就是它的消耗远比它带来的效益要大得多。如果您想了解更多的话,那就继续多下去。

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

推荐阅读更多精彩内容

  • com.google.common.cache 1、背景 缓存,在我们日常开发中是必不可少的一种解决性能问题的方法...
    拾壹北阅读 22,164评论 0 25
  • CPU Cache 今天的CPU比25年前更复杂。那时候,CPU内核的频率与内存总线的频率相当。内存访问只比寄存器...
    blueshadow阅读 2,965评论 0 5
  • Google Guava Cache是一种非常优秀本地缓存解决方案,提供了基于容量,时间和引用的缓存回收方式。基于...
    Acamy丶阅读 25,693评论 3 34
  • 概述 缓存是日常开发中经常应用到的一种技术手段,合理的利用缓存可以极大的改善应用程序的性能。Guava官方对Cac...
    小陈阿飞阅读 1,761评论 0 0
  • 今天是大年三十,回家已经好几天了,”其乐融融“至少表面上看起来是如此...... 家里很舒适,我的大床,暖和的被子...
    谭胜阅读 225评论 0 0