Google的Guava cache 缓存使用

[url]http://xpchenfrank.iteye.com/category/220687[/url]
官方的翻译:[url]http://cd826.iteye.com/blog/2036659[/url]
[color=red]Guava学习笔记:Guava cache[/color] [url]http://www.cnblogs.com/peida/p/Guava_Cache.html[/url]


Guava库学习:学习Guava Cache知识汇总 [url]http://my.oschina.net/realfighter/blog/358871[/url]


适用范围
Cache在真实场景中有着相当广的使用范围。例如,当一个值要通过昂贵的计算和检索来获取,并且这个结果会被多次使用到,这个时候你就应该考虑使用缓存了。 Cache有点类似于ConcurrentMap,但并不是完全一样。最根本的区别在于ConcurrentMap会持有添加到Map中的所有元素直到元素被移除为止。Cache通过相关的配置项可以自动驱逐相关的缓存项,达到约束缓存占用的目的。有些情景中LoadingCache通过自动的内存载入甚至可以不进行驱逐缓存项。 通常,Guava缓存工具适用于如下的情景:
[b][color=blue]1、你愿意牺牲一些内存来提升速度。
2、你期待缓存项的查询大于一次。
3、缓存不需要存储在除了内存以外的更多数据。[/color][/b]
如果你满足以上的情景,那么Guava Cache工具集会是你的正确选择。 获取一个Cache实例,可以通过CacheBuilder的工厂模式来获得,如上面所示的示例代码,但是定制你的Cache才是真正有趣的部分。 注意:如果你不需要Cache的特性,ConcurrentHashMap会更高效的使用内存,但是ConcurrentHashMap就很难实现缓存特性。

[color=red]Guava Cache 创建[/color]
基本上可以通过两种方式来创建cache:
[color=blue]cacheLoader
callable callback[/color]
通过这两种方法创建的cache,和通常用map来缓存的做法比,不同在于,这两种方法都实现了一种逻辑——从缓存中取key X的值,如果该值已经缓存过了,则返回缓存中的值,如果没有缓存过,可以通过某个方法来获取这个值。

但不同的在于cacheloader的定义比较宽泛,是针对整个cache定义的,可以认为是统一的根据key值load value的方法。

而callable的方式较为灵活,允许你在get的时候指定。

下面是两种方法的例子:

首先是基于cacheloader的方法
@Test  
public void testCacheBuilder() throws ExecutionException {

LoadingCache cache = CacheBuilder.newBuilder().maximumSize(1000).expireAfterAccess(3, TimeUnit.SECONDS)
.build(new CacheLoader(){
@Override
public Book load(String key) throws Exception {
Book b = new Book();
return b;
}
})
;

Book book = cache.get("testKey");
System.out.println("1 time value is: " + book);
Thread.currentThread();
Thread.sleep(TimeUnit.SECONDS.toMillis(2)); //还是在3秒之内读取
Book book1 = cache.get("testKey");
System.out.println("2 time value is: " + book1);

Thread.currentThread();
Thread.sleep(TimeUnit.SECONDS.toMillis(3)); //第二次读取的时候继续缓存3秒计算。到这里的时候刚好是3秒,就是说超时读取,会得到新对象。当这里是toMillis(2)的话,还是在3秒缓存之内,就会得到旧内容的。
Book book2 = cache.get("testKey");
System.out.println("6 time value is: " + book2);
}

}

输出:
1 time value is: com.cache.Book@10b9d04
2 time value is: com.cache.Book@10b9d04
6 time value is: com.cache.Book@fa9cf

其次是基于实现callable的方法:
@Test  
public void testCallable() throws ExecutionException {
// 没有使用CacheLoader
Cache cache = CacheBuilder.newBuilder().maximumSize(1000).build();

String resultVal = cache.get("testKey", new Callable() {
public String call() {
// 这里先根据key实际去取值的方法,例如根据这个key去数据库或者properties文件中取值
ApplicationContext context = new FileSystemXmlApplicationContext("E:/WorkDir/struts2practice/GuavaTest/WebRoot/WEB-INF/xml/springConfig.xml");
JdbcCustomerDAO aJdbcCustomerDAO = context.getBean(JdbcCustomerDAO.class);

System.out.println("resultVal call method is invoked");
return aJdbcCustomerDAO.findValue("testKey");
}
});
System.out.println("first time value is: " + resultVal);

String resultVal1 = cache.get("testKey", new Callable() {
public String call() {
// 这里先根据key实际去取值的方法,例如根据这个key去数据库或者properties文件中取值
ApplicationContext context = new FileSystemXmlApplicationContext("E:/WorkDir/struts2practice/GuavaTest/WebRoot/WEB-INF/xml/springConfig.xml");
JdbcCustomerDAO aJdbcCustomerDAO = context.getBean(JdbcCustomerDAO.class);

System.out.println("resultVal1 call method is invoked");
return aJdbcCustomerDAO.findValue("testKey");
}
});
System.out.println("second time values is: " + resultVal1);
}


[size=x-large][color=blue]Guava Cache 数据的移除[/color][/size]
[color=red][size=large]被动移除数据的方式[/size][/color],guava默认提供了三种方式:
[b]基于大小的移除[/b]
看字面意思就知道就是按照缓存的大小来移除,如果即将到达指定的大小,那就会把不常用的键值对从cache中移除。
定义的方式一般为 CacheBuilder.maximumSize(long),官方还介绍了一种可以算权重的方法,个人认为实际使用中不太用到,暂不讨论。
就这个常用的来看有几个注意点,
其一,这个size指的是cache中的条目数,不是内存大小或是其他;
其二,并不是完全到了指定的size系统才开始移除不常用的数据的,而是接近这个size的时候系统就会开始做移除的动作;
其三,如果一个键值对已经从缓存中被移除了,你再次请求访问的时候,如果cachebuild是使用cacheloader方式的,那依然还是会从cacheloader中再取一次值,如果这样还没有,就会抛出异常


[b]基于时间的移除[/b]
guava提供了两个基于时间移除的方法
expireAfterAccess(long, TimeUnit) 这个方法是根据某个键值对最后一次访问之后多少时间后移除
expireAfterWrite(long, TimeUnit) 这个方法是根据某个键值对被创建或值被替换后多少时间移除

[b]基于引用的移除[/b]
这种移除方式主要是基于java的垃圾回收机制,根据键或者值的引用关系决定移除,个人对垃圾回收这块不是非常了解,窃以为不太可靠。。也不常用。。所以没有研究,欢迎补充。


[size=large][color=red]主动移除数据方式[/color][/size]
主动移除有三种方法:
[i]单独移除用 Cache.invalidate(key)
批量移除用 Cache.invalidateAll(keys)
移除所有用 Cache.invalidateAll()[/i]

如果需要在移除数据的时候有所动作还可以定义Removal Listener,但是有点需要注意的是默认Removal Listener中的行为是和移除动作同步执行的,如果需要改成异步形式,可以考虑使用RemovalListeners.asynchronous(RemovalListener, Executor)

[color=red]Guava Cache 的清空,刷新及统计功能[/color]
主要介绍guava cache的清空,刷新和统计的功能。
[b]缓存数据的清空[/b]
guava没有提供自动触发清空缓存数据的功能,而是提供了一种手工调用的方式,使用者需要通过Cache.cleanUp()的方式来清空缓存。

所以一般可以有两种选择,一种是通过某个请求来触发清空动作,这种相当于按需清空,另一种则是通过定时任务,亦成为调度程序来清空,这种相当于与按时清空

[b]缓存数据的刷新 [/b]
guava没有提供类似refreshall的方法刷新缓存中的所有值,而只是提供了 LoadingCache.refresh(K)方法,用于刷新某个键值对,这里有趣的是刷新动作是异步的,也就是在值被彻底刷新之前,如果有人取这个key的值,返回的还是没有刷新的值。

如果你希望定义自己的刷新行为,可以重写 CacheLoader.reload(K, V)方法
LoadingCache graphs = CacheBuilder.newBuilder()  
.maximumSize(1000)
.refreshAfterWrite(1, TimeUnit.MINUTES)
.build(
new CacheLoader() {
public Graph load(Key key) { // no checked exception
return getGraphFromDatabase(key);
}

public ListenableFuture reload(final Key key, Graph prevGraph) {
if (neverNeedsRefresh(key)) {
return Futures.immediateFuture(prevGraph);
} else {
// asynchronous!
return ListenableFutureTask.create(new Callable() {
public Graph call() {
return getGraphFromDatabase(key);
}
});
}
}
});


[b]缓存数据统计[/b]
可以通过 CacheBuilder.recordStats()方法打开统计, Cache.stats()方法会返回一个CacheStats对象,里面有缓存条目访问率等数据,如果你的缓存需要做一些调优,可以参考这里的数据。

你可能感兴趣的:(Java,开源应用)