想对Guava cache部分进行总结,但思索之后,文档才是最全面、详细的。所以,决定对guava文档进行翻译。

英文地址如下:https://github.com/google/guava/wiki/CachesExplained

花费了一些时间进行翻译,翻译的水平有待提高,有些地方翻译的不准确,因为有些没有实际用到,所以无法给出清晰的解释。

如果对您有帮助,莫感欣慰!!!

一 概要

Guava cache是google开发的,目前被常用在单机上,如果是分布式,它就无能为力了。废话不多说,下面开始进入正文。

二内存解释

Example

 LoadingCache<Key, Graph> graphs = CacheBuilder.newBuilder()
.maximumSize(1000)
.expireAfterWrite(10, TimeUnit.MINUTES)
.removalListener(MY_LISTENER)
.build(
new CacheLoader<Key, Graph>() {
public Graph load(Key key) throws AnyException {
return createExpensiveGraph(key);
}
});

应用性

缓存在许多的地方非常的有用。比如:当一个计算或是查询一个值花费很大代价时,或者,你需要多次用到一个值时,你应该考虑使用缓存。
Cache 跟ConcurrentMap 很像,但不一样。最大的功能上的区別,ConcurrentMap允许所有的元素直到被手动移除为止,一直存在。另一方面,Cache为了限制内存的占用,通常会自动地移除值。某些时候,LoadingCache 即使不驱除元素,但由于他自动导入缓存的特点,它也是十分有用的。

一般情况下,当满足以下场景时:
・希望花费一下内存来提高速度
・有些keys会被多次查询
・你的cache保存的东西不会超过你机器的内存量
此时,你应该选择Guava cache

获得一个Cache 用上面的code例子就可以了,但是自定义一个cache 会更有趣。

渲染

问自己关于你的内存的第一个问题应该是:有什么默认的方法来导入或是计算key的值吗。如果是这样的话,你应使用CacheLoader 。如果不是的话,或者说,你需要覆盖掉默认的方法,但你仍想保留“存在就直接获取,不存在就去计算”这种机制时,你应该往get方法调用中传一个callable 。使用Cache.put 可以直接插入元素,但是从所有数据缓存一致性方面来说,使用自动的缓存导入方法更加简单。

使用CacheLoader

一个LoadingCache 就是关联了一个CacheLoader 的缓存。创建一个CacheLoader 就跟实现方法V load(K key) throws Exception 一样简单。你可以用如下的例子来创建LoadingCache :

 LoadingCache<Key, Graph> graphs = CacheBuilder.newBuilder()
.maximumSize(1000)
.build(
new CacheLoader<Key, Graph>() {
public Graph load(Key key) throws AnyException {
return createExpensiveGraph(key);
}
}); ...
try {
return graphs.get(key);
} catch (ExecutionException e) {
throw new OtherException(e.getCause());
}

查询LoadingCache 的权威方法是用get(K) 。如果已经换存了值,就会直接返回;如果没有,就会使用CacheLoader 来往缓存中自动导入一个新值。因为CacheLoader 会抛出Exception ,LoadingCache.get(K)可能会抛出ExecutionException 。你也可以用getUnchecked(K) ,它在UncheckedExecutionException 中包装了所有的UncheckedExecutionException ,但是,如果CacheLoader 抛出了 checked exceptions的话,会导致奇怪的行为发生。

 LoadingCache<Key, Graph> graphs = CacheBuilder.newBuilder()
.expireAfterAccess(10, TimeUnit.MINUTES)
.build(
new CacheLoader<Key, Graph>() {
public Graph load(Key key) { // no checked exception
return createExpensiveGraph(key);
}
}); ...
return graphs.getUnchecked(key);

体积查询可以用方法getAll(Iterable<? extends K>) 。默认情况下,getAll 会对CacheLoader.load 产生一个单独的调用,对cache中每个不存在缓存值的key ,进行取值。当体积的查询已经比单个查询效率更高时,你可以通过覆盖CacheLoader.loadAll 方法,来开发它。

注意:你可以写一个CacheLoader.loadAll 的实现为那些没有特殊指定的key来导入值。比如:如果计算某些group中的任意key的值,会给你group内所有key的值,loadAll 也许会同时导入group内其他key的值。

From a Callable

所有Guava缓存,无论是否是导入,都支持get(K, Callable<V>) 方法。这个方法返回内存中这个key关联的值,或是用Callable 接口计算得到的值并将它加入内存中。知道load() 使用,对内存的修改才有了一个可观察的状态。这个方法为“如果缓存了,返回缓存之;没有缓存则创建,缓存并放回”这个模式提供了一个简单的替代品。

 Cache<Key, Value> cache = CacheBuilder.newBuilder()
.maximumSize(1000)
.build(); // look Ma, no CacheLoader
...
try {
// If the key wasn't in the "easy to compute" group, we need to
// do things the hard way.
cache.get(key, new Callable<Value>() {
@Override
public Value call() throws AnyException {
return doThingsTheHardWay(key);
}
});
} catch (ExecutionException e) {
throw new OtherException(e.getCause());
}

直接插入
值必须用cache.put(key, value) 方法来插入到缓存中。这个覆写了内存中制定key的元素。值的变化也可以使用被Cache.asMap() 暴露出来的、ConcurrentMap 的任意的一个方法。注意的是,asMap 视图中没有任何方法会让键值对自动导入到内存中,所以使用Cache.get(K, Callable<V>) 与使用CacheLoader 或是 Callable 来导入内存的Cache.asMap().putIfAbsent相比,前者更好。

驱逐
残酷的事实是我们没有足够的内存缓存所有东西。你必须决定:何时内存值不值得保存了。Guava 提供三种驱逐方式:基于大小,基于时间,基于引用。

容量驱逐
如果你缓存的值的数量不应该超过一定的数量,那么就用CacheBuilder.maximumSize(long) 方法。缓存会驱逐最近没被使用的,或是不常用的。警告:内存可能会在数量超过前,将键值对驱逐,基本上是当数量达到限定值。

如果内存的键值对有不通的权重时,它们会交替执行,比如:如果你的内存值有完全不同的内存覆盖范围,你可以制定一个权重的函数CacheBuilder.weigher(Weigher) 和一个最大缓存权重的函数CacheBuilder.maximumWeight(long) 。此外,正如maximumSize 所要求的,要意识到权重时每回创建时计算的,并且,那之后,是静态的。

 LoadingCache<Key, Graph> graphs = CacheBuilder.newBuilder()
.maximumWeight(100000)
.weigher(new Weigher<Key, Graph>() {
public int weigh(Key k, Graph g) {
return g.vertices().size();
}
})
.build(
new CacheLoader<Key, Graph>() {
public Graph load(Key key) { // no checked exception
return createExpensiveGraph(key);
}
});

超时驱逐
CacheBuilder 提供两种超时驱逐:
expireAfterAccess(long, TimeUnit) 只用最后被读过或是写过的内存,经历过存活时间之后,才会死亡。注意键值对被驱逐的时间容量驱逐很相似。
expireAfterWrite(long, TimeUnit) 当被创建的键值对或是最近被替换过的,经过一段存活期间后,会走向死亡。这个可用于经历过一段期间后,缓存的数据变得过期数据,这样场景下使用。

Testing Timed Eviction

测试超时驱逐不是很难,也不必花上2秒钟去测试一个2秒超时。使用Ticker 接口和 CacheBuilder.ticker(Ticker) 方法在你的cache 中指定时间,而不是去等待系统时钟的2秒。

基于引用的驱逐
Guava 允许你建立基于垃圾回收的缓存,可以使用弱引用和软引用。
(注:Java中的引用分为四种:强、软、弱、虚
强引用:Java之中普遍存在,如Object object = new Object() 只要引用存在,垃圾回收器永远不会回收掉被引用的对象
软引用:描述一些有用,但非必须的对象。在系统将要发生内存溢出时,会将这些对象放进回收范围之内,进行回收
弱引用:描述非必需的对象,强度比软引用弱,无论当前内存是否充足,垃圾回收时都会对其进行回收
虚医用:最弱的一种引用关系。设置虚引用,唯一的目的就是,在这个对象呗收集器回收时收到一个系统通知
引自《深入理解Java虚拟机-周志华 )

・CacheBuilder.weakKeys() 使用弱引用来保存key值。如果没有其他引用指向这个key,那么它将允许被垃圾收集器回收掉。既然垃圾回收仅依赖于恒等式的一致,这就导致整个缓存用 == 来比较key,而不是equals()。
・CacheBuilder.weakValues() 使用弱引用来保存value值。如果没有其他引用指向这个value,那么它将允许被垃圾收集器回收掉。既然垃圾回收仅依赖于恒等式的一致,这就导致整个缓存用 == 来比较value,而不是equals()。
・CacheBuilder.softValues() 用软引用包装值。应对内存的需求,软引对象使用最近最少使用条例,来进行垃圾回收。因为使用软引用的性能上的关系,我们通常建议使用最大缓存数量。softValues() 的使用会导致使用整个缓存用 == 比较value,而不是equals()。

监视移除
你会制定一个监视器,可以通过CacheBuilder.removalListener(RemovalListener) ,来监视键值对在缓存中被移除。RemovalListener 获得了一个RemovalNotification, 它指定了RemovalCause ,键和值。
注意,任何被RemovalListener 抛出的异常都会被打进log里。

 CacheLoader<Key, DatabaseConnection> loader = new CacheLoader<Key, DatabaseConnection> () {
public DatabaseConnection load(Key key) throws Exception {
return openConnection(key);
}
};
RemovalListener<Key, DatabaseConnection> removalListener = new RemovalListener<Key, DatabaseConnection>() {
public void onRemoval(RemovalNotification<Key, DatabaseConnection> removal) {
DatabaseConnection conn = removal.getValue();
conn.close(); // tear down properly
}
}; return CacheBuilder.newBuilder()
.expireAfterWrite(2, TimeUnit.MINUTES)
.removalListener(removalListener)
.build(loader);

警告:监视器的操作默认是同步的,因此,内存的保持一般来说都是正常操作。花费(时间)较大的监视器会拖慢缓存的功能。如果,你有一个花费(时间)较大的监视器,异步地使用RemovalListeners.asynchronous(RemovalListener, Executor) 来装饰RemovalListener 。

什么时候发生清空操作?
用CacheBuilder 建立的缓存不会发生清空,不会自动驱逐值,不会当值过期后立即清除,不会清除任何排序的东西。相反,在读写操作发生后,它会有短暂的保留。

原因如下:如果要缓存一直可用,那么我们需要创建一个线程,它的操作需要user的操作来完成。此外,一些环境限制我们创建线程,这样,会导致CacheBuilder 不可用。
相反呢,我们让您来决定。如果缓存有比较高的吞吐量,那么你不必担心缓存一直可用会清理掉过期的键值对。如果你的缓存,仅仅的写操作,你不想让清空来锁住缓存的读取,你会希望创建你自己的保持线程,以常规的间隔来调用Cache.cleanUp() 。
如果你想为几乎只有写操作的缓存来定制常规的内存保持,那么就用ScheduledExecutorService 。

刷新
刷新和驱逐不太一样。正如LoadingCache.refresh(K) 中指定的,刷新key导入一个新值,可能是异步地操作。和驱逐做对比,当刷新时,强制查询直到获取新值时,返回的仍是旧值。
如果刷新时有异常发生,异常会被记录在log中。
CacheLoader 会以通过覆盖CacheLoader.reload(K, V) 这个方法来使用刷新。这个方法允许你在计算新值时使用旧值。

 // Some keys don't need refreshing, and we want refreshes to be done asynchronously.
LoadingCache<Key, Graph> graphs = CacheBuilder.newBuilder()
.maximumSize(1000)
.refreshAfterWrite(1, TimeUnit.MINUTES)
.build(
new CacheLoader<Key, Graph>() {
public Graph load(Key key) { // no checked exception
return getGraphFromDatabase(key);
} public ListenableFuture<Graph> reload(final Key key, Graph prevGraph) {
if (neverNeedsRefresh(key)) {
return Futures.immediateFuture(prevGraph);
} else {
// asynchronous!
ListenableFutureTask<Graph> task = ListenableFutureTask.create(new Callable<Graph>() {
public Graph call() {
return getGraphFromDatabase(key);
}
});
executor.execute(task);
return task;
}
}
});

使用CacheBuilder.refreshAfterWrite(long, TimeUnit) 方法可以将时间刷新加入到缓存中。和expireAfterWrite 相比较,refreshAfterWrite 会让一个值在指定的时间段之后进行刷新,但是刷新也只有当键值对被查询时才会开始。所以,举例子来说,你可以同时指定refreshAfterWrite 和 expireAfterWrite ,所以当键值对可以被刷新时,驱逐计时器不会盲目地被重置,所以,当一个键值对可以被刷新时,但是此时没有被查询,那么,它将会被驱逐。

特性

统计数据
通过CacheBuilder.recordStats() 你可以为Guava 缓存打开数据收集。Cache.stats() 方法返回一个Cache.stats() 对象,这个对象提供了统计数据,如:
・hitRate() 返回采样数的比率
・averageLoadPenalty() 导入新值平均花费时长 单位:纳秒
・evictionCount() 缓存驱逐的个数
此外还有许多其他的统计数据。这些统计数据在缓存优化方面启动决定性作用,我们建议在性能很重要的应用中,留心这些统计数据。

asMap
你可以将缓存看做是一个使用asMap 视图的ConcurrentMap 。但是,asMap 视图和缓存如何交互需要下面的一些解释。
・cache.asMap() 包含了所有现在导入缓存中的键值对。所以,比如,cache.asMap().keySet() 包含了所有导入的key
・asMap().get(key) 本质上与cache.getIfPresent(key) 相等,从不会引起值的导入。这个和Map相比,是一致的。
・读写操作会导致access time被重置。但containsKey(Object) 和Cache.asMap() 操作不会导致重置发生。举例子来说,用cache.asMap().entrySet() 来迭代不会导致access time被重置。

中断

像get() 这样的导入方法永远不会抛出InterruptedException。不过,我们可以设计这些方法来支持InterruptedException 。但是,我们的支持并不是完整的,强制地在所用用户上产生花销只会收益很少。具体来说,比如读取。

get 把那些请求的、未缓存的值大体分为两类:那些导入的的值和那些等待另一个线程导入的值。这两者以不同方式支持中断。简单的方法是等待另一个正在执行的线程完事后,再进行导入。这里呢,我们就会进入可中断的等待。比较难的方法是我们自己导入值。我们用用户定义的CacheLoader 。如果它支持中断,那么我们可以支持中断;如果不行,那么我们也不能支持中断。

那么为什么当提供的CacheLoader 支持中断,而自定义的不支持呢?某种意义上来说,我们支持中断。如果CacheLoader 抛出中断异常,所有关于key 的调用会立即返回。此外,get 会在导入线程中存储中断标记位。惊奇的是,InterruptedException 被包装在ExecutionException 中。

原则上讲,我们可以不为你包装这个异常。然而,这将导致强迫所有LoadingCache 用户去处理InterruptedException ,即使是那些从未抛出中断异常的、CacheLoader 的实现。也许你考虑那些非导入线程的登台可以诶中断是值得的,但是需要缓存只是单一线程。他们额用户必须仍要catch不可能的InterruptedException 。

在这部分我们的原则是让缓存在所有调用的线程中导入值。这个原则让每个调用中再计算值变得简单。如果旧代码不可被中断,那么,或许对于新代码来说也是不可被中断。

我说过我们在某种意义上支持中断。在另一层(让LoadingCache 作为有漏洞的抽象)来说,我们不支持中断。如果导入线程被中断了,我们很可能将这个异常看做其他异常。这个,在很多地方来说,没有大碍。但是当多次调用get 等待返回值时,就会出错。虽然,刚巧要计算值得操作被中断了,其他的需要这个值的一些操作不会被执行。然而,这些调用者收到InterruptedException (包装在ExecutionException中), 即使导入没有将失败作为终止。正确的行为将是遗留下来的一个线程再次进行尝试。关于我们有个一个bug列表(https://github.com/google/guava/issues/1122)。然而,修正的话也有一定风险。并非是修正问题,我们会投入额外的精力到被推荐的AsyncLoadingCache 中,它面对中断会做出正确的行为,同时返回Future 对象。

Guava Cache 总结的更多相关文章

  1. Spring cache简单使用guava cache

    Spring cache简单使用 前言 spring有一套和各种缓存的集成方式.类似于sl4j,你可以选择log框架实现,也一样可以实现缓存实现,比如ehcache,guava cache. [TOC ...

  2. [Java 缓存] Java Cache之 Guava Cache的简单应用.

    前言 今天第一次使用MarkDown的形式发博客. 准备记录一下自己对Guava Cache的认识及项目中的实际使用经验. 一: 什么是Guava Guava工程包含了若干被Google的 Java项 ...

  3. Guava学习笔记:Guava cache

    缓存,在我们日常开发中是必不可少的一种解决性能问题的方法.简单的说,cache 就是为了提升系统性能而开辟的一块内存空间. 缓存的主要作用是暂时在内存中保存业务系统的数据处理结果,并且等待下次访问使用 ...

  4. Ehcache与Guava Cache的区别浅谈

    最近在做一些缓存改造的场景,有如下一些经验总结: 缓存版本: Ehcache:2.8.3 Guava:17.0 Ehcache支持持久化到本地磁盘,Guava不可以: Ehcache有现成的集群解决方 ...

  5. guava cache

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

  6. 第七章 企业项目开发--本地缓存guava cache

    1.在实际项目开发中,会使用到很多缓存技术,而且数据库的设计一般也会依赖于有缓存的情况下设计. 常用的缓存分两种:本地缓存和分布式缓存. 常用的本地缓存是guava cache,本章主要介绍guava ...

  7. (翻译)Google Guava Cache

    翻译自Google Guava Cache This Post is a continuation of my series on Google Guava, this time covering G ...

  8. 是什么让spring 5放弃了使用Guava Cache?

    一路走来,Spring社区从刚开始的核心模块一直发展到现在,最近Sping5也完成了M5的发布, 相信不久之后第一个RELEASE版本也会发布.里面有很多特性是和即将要发布的JAVA 9息息相关的.今 ...

  9. Guava Cache源码解析

    概述: 本次主要是分析cache的源码,基本概念官方简介即可. 基本类图: 在官方的文档说明中,Guava Cache实现了三种加载缓存的方式: LoadingCache在构建缓存的时候,使用buil ...

  10. google guava cache缓存基本使用讲解

    代码地址:https://github.com/vikde/demo-guava-cache 一.简介 guava cache是google guava中的一个内存缓存模块,用于将数据缓存到JVM内存 ...

随机推荐

  1. Win10系列:VC++媒体播放控制1

    在MediaElement控件中定义了用于控制视频播放的函数,如Play.Pause和Stop等函数.本小节将在20.6.1小节所新建的项目基础上继续来介绍如何为视频添加播放控制,并在最后一部分给出项 ...

  2. C++解析五-this 指针,指向类的指针

    C++this指针 在 C++ 中,每一个对象都能通过 this 指针来访问自己的地址.this 指针是所有成员函数的隐含参数.因此,在成员函数内部,它可以用来指向调用对象.友元函数没有 this 指 ...

  3. mac以及centos下安装Elasticsearch 以及权限管理插件

    Elasticsearch安装(提前系统需要安装java环境)mac安装 brew install elasticsearch centos安装 下载ElasticSearch安装包,https:// ...

  4. [Linux]Linux下修改snmp协议的默认161端口

    一.Linux SNMP的配置 SNMP的简介和Linux下IPV4,IPV6地址的snmp协议开启可以参考上一个随笔:[Linux]CentOS6.9开启snmp支持IPV4和IPV6 二.修改默认 ...

  5. 准备下上机考试,各种排序!!以后再添加和仿真像wiki上那样!

    #include <stdio.h> #include <string.h> #define N 6 typedef struct { ]; int score; }stude ...

  6. Class file collision

    ecplise报错,提示:Class file collision (类文件冲突) 原因是:文件保存(编译)后,生成了class文件起了冲突,windows 系统认为Test.class 和test. ...

  7. 【转】Delphi XE10 Android Splash设备自适应和沉浸式状态条

    再次提笔写博客,已经相隔7年,原来的CSDN账号需要手机验证,而我的手机又捆绑到这个账号了,就用新账号吧,不想折腾了. 原账号的帖子,有研究DICOM3.0的可以看下:http://blog.csdn ...

  8. [python]操作redis sentinel以及cluster

    先了解清楚sentinel和cluster的差别,再学习使用python操作redis的API,感觉会更加清晰明白. 1.redis sentinel和cluster的区别 sentinel遵循主从结 ...

  9. express依赖中模块引擎的使用

    express中模块引擎的切换 4.x 示例: 如果要将默认的模块引擎切换至指定的模块引擎,用layout render.get('/',function(req,res,next){ res.ren ...

  10. 行为参数化和Lambda表达式

    行为参数化是指拿出一个代码块把他准备好却不执行它.这个代码块以后可以被程序的其他部分调用,意味着你可以推迟这块代码的执行.方法接受多种行为作为参数,并在内部使用来完成不同的行为.行为参数话的好处在于可 ...