Guava Cache 总结
想对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 总结的更多相关文章
- Spring cache简单使用guava cache
Spring cache简单使用 前言 spring有一套和各种缓存的集成方式.类似于sl4j,你可以选择log框架实现,也一样可以实现缓存实现,比如ehcache,guava cache. [TOC ...
- [Java 缓存] Java Cache之 Guava Cache的简单应用.
前言 今天第一次使用MarkDown的形式发博客. 准备记录一下自己对Guava Cache的认识及项目中的实际使用经验. 一: 什么是Guava Guava工程包含了若干被Google的 Java项 ...
- Guava学习笔记:Guava cache
缓存,在我们日常开发中是必不可少的一种解决性能问题的方法.简单的说,cache 就是为了提升系统性能而开辟的一块内存空间. 缓存的主要作用是暂时在内存中保存业务系统的数据处理结果,并且等待下次访问使用 ...
- Ehcache与Guava Cache的区别浅谈
最近在做一些缓存改造的场景,有如下一些经验总结: 缓存版本: Ehcache:2.8.3 Guava:17.0 Ehcache支持持久化到本地磁盘,Guava不可以: Ehcache有现成的集群解决方 ...
- guava cache
适用场景 缓存在很多场景下都是相当有用的.例如,计算或检索一个值的代价很高,并且对同样的输入需要不止一次获取值的时候,就应当考虑使用缓存. Guava Cache与ConcurrentMap很相似,但 ...
- 第七章 企业项目开发--本地缓存guava cache
1.在实际项目开发中,会使用到很多缓存技术,而且数据库的设计一般也会依赖于有缓存的情况下设计. 常用的缓存分两种:本地缓存和分布式缓存. 常用的本地缓存是guava cache,本章主要介绍guava ...
- (翻译)Google Guava Cache
翻译自Google Guava Cache This Post is a continuation of my series on Google Guava, this time covering G ...
- 是什么让spring 5放弃了使用Guava Cache?
一路走来,Spring社区从刚开始的核心模块一直发展到现在,最近Sping5也完成了M5的发布, 相信不久之后第一个RELEASE版本也会发布.里面有很多特性是和即将要发布的JAVA 9息息相关的.今 ...
- Guava Cache源码解析
概述: 本次主要是分析cache的源码,基本概念官方简介即可. 基本类图: 在官方的文档说明中,Guava Cache实现了三种加载缓存的方式: LoadingCache在构建缓存的时候,使用buil ...
- google guava cache缓存基本使用讲解
代码地址:https://github.com/vikde/demo-guava-cache 一.简介 guava cache是google guava中的一个内存缓存模块,用于将数据缓存到JVM内存 ...
随机推荐
- mq(1):简介
1.mq的使用场景 以前的我,一直都没太搞明白,为什么我们那么需要消息队列,直到我看到了网友scienjus.的这个例子. 例子:假设用户在你的软件中注册,服务端收到用户的注册请求后,它会做这些操作: ...
- Innodb引擎简介
一.锁 二.什么情况出现阻塞 1.频繁更改的表,出现了慢查询 2.频繁访问的表,出现了备份等(表级锁) 三.查看运行情况 show engine innodb status; 四.关键参数 innod ...
- 7.6 C++基本序列式容器效率比较
参考:http://www.weixueyuan.net/view/6403.html 总结: 对于vector而言,它只是一个可以伸缩长度的数组 对于deque而言,它是一个可以操作头部和尾部的并且 ...
- 小波学习之一(单层一维离散小波变换DWT的Mallat算法C++和MATLAB实现) ---转载
1 Mallat算法 离散序列的Mallat算法分解公式如下: 其中,H(n).G(n)分别表示所选取的小波函数对应的低通和高通滤波器的抽头系数序列. 从Mallat算法的分解原理可知,分解后的序 ...
- ios表单验证帮助类
// // ValidateHelper.h // #import <Foundation/Foundation.h> @interface ValidateHelper : NSObje ...
- FCC JS基础算法题(6):Truncate a string(截断字符串)
先看一下题目描述: 如果字符串的长度比指定的参数num长,则把多余的部分用...来表示.切记,插入到字符串尾部的三个点号也会计入字符串的长度.但是,如果指定的参数num小于或等于3,则添加的三个点号不 ...
- HIVE点滴:选择两个字段时distinct位置的影响
当选择两个字段时,例如:"select XX1, XX2 from tb; ",那么将distinct放在前一个字段XX1之前和放在后一个字段XX2之前,结果有什么不同呢? 先说结 ...
- python day07作业
- IE8的input兼容性问题
在chrome.firefox.IE9+都是支持input事件 在IE8中,单纯的input事件无法监听输入框中变化,需要与propertychange共用 测试代码如下: <!DOCTYPE ...
- 使用golang编写prometheus metrics exporter
metrcis输出 collector.go package main import ( "github.com/prometheus/client_golang/prometheus&qu ...