如何使用Guava的缓存管理

如何使用Guava的缓存管理,第1张

首先,看一下使用范例:

Java代码

LoadingCache<Key,Graph> graphs =CacheBuildernewBuilder()

maximumSize(1000)

expireAfterWrite(10,TimeUnitMINUTES)

removalListener(MY_LISTENER)

build(

newCacheLoader<Key,Graph>(){

publicGraph load(Key key)throwsAnyException{

return createExpensiveGraph(key);

}

});

适用性

缓存在很多情况下都是非常有用的。比如,我们需要多次根据给定的输入获取值,而且该值计算或者获取的开销是非常昂贵的。

缓存和ConcurrentMap是非常相像的,但是它们也不完全一样。最根本的区别就是,ConcurrentMap会持有所有添加的对象,直到被显示的移除。而缓存为了限制其内存的使用,通常都会配置成可以自动的将对象移除。在某些情况下即使不自动移除对象也是非常有用的,如LoadingCache它会自动加载缓存对象。

一般,Guava缓存适用于以下几种情况:

你愿意花费一些内存来换取性能提升;

你预测到某些键会多次进行查询;

你的缓存数据不超过内存(Guava缓存是单个应用中的本地缓存。它不会将数据存储到文件中,或者外部服务器。如果不适合你,可以考虑一下 Memcached)。

如果你的需要符合上面所说的每一条,那么选择Guava缓存绝对没错。

使用CacheBuilder的构建模式可以获取一个Cache,如上面的范例所示。但是如何进行定制才是比较有趣的。

注意:如果你不需要缓存的这些特性,那么使用ConcurrentHashMap会有更好的内存效率,但是如果想基于旧有的ConcurrentMap复制实现Cache的一些特性,那么可能是非常困难或者根本不可能。

加载

对于缓存首先需要明确的是:有没有一个方法可以通过给定的键来计算/加载相应的值?如果有,那么可以使用CacheLoader。如果没有这样的方法,或者你想复写缓存的加载方式,但你仍想保留“get-if-absent-compute”语义,你可以在调用get方法时传入一个Callable实例,来达到目的。缓存的对象可以通过Cacheput直接插入,但是自动加载是首选,因为自动加载可以更加容易的判断所有缓存信息的一致性。

From a CacheLoader

LoadingCache 缓存是通过一个CacheLoader来构建缓存。创建一个CacheLoader仅需要实现V load(K key) throws Exception方法即可。下面的范例就是如何创建一个LoadingCache:

Java代码

LoadingCache<Key,Graph> graphs =CacheBuildernewBuilder()

maximumSize(1000)

build(

newCacheLoader<Key,Graph>(){

publicGraph load(Key key)throwsAnyException{

return createExpensiveGraph(key);

}

});

try{

return graphsget(key);

}catch(ExecutionException e){

thrownewOtherException(egetCause());

}

通过方法get(K)可以对LoadingCache进行查询。该方法要不返回已缓存的值,要不通过CacheLoader来自动加载相应的值到缓存中。这里需要注意的是:CacheLoader可能会抛出Exception,LoaderCacheget(K)则可能会抛出ExecutionException。假如你定义的CacheLoader没有声明检查型异常,那么可以通过调用getUnchecked(K)来获取缓存值;但是一旦当CacheLoader中声明了检查型异常,则不可以调用getUnchecked。

Java代码

LoadingCache<Key,Graph> graphs =CacheBuildernewBuilder()

expireAfterAccess(10,TimeUnitMINUTES)

build(

newCacheLoader<Key,Graph>(){

publicGraph load(Key key){// no checked exception

return createExpensiveGraph(key);

}

});

return graphsgetUnchecked(key);

批量查询可以使用getAll(Iterable< extends K>)方法。缺省,getAll方法将循环每一个键调用CacheLoaderload方法获取缓存值。当缓存对象的批量获取比单独获取更有效时,可以通过复写CacheLoaderloadAll方法实现缓存对象的加载。此时当调用getAll(Iterable)方法时性能也会提升。

需要注意的是CacheLoaderloadAll的实现可以为没有明确要求的键加载缓存值。比如,当为某组中的一些键进行计算时,loadAll方法则可能会同时加载组中其余键的值。

From a Callable

所有Guava缓存,不论是否会自动加载,都支持get(K, Callable(V))方法。当给定键的缓存值已存在时则直接返回,否则通过指定的Callable方法进行计算并将值存放到缓存中。直到加载完成时,相应的缓存才会被更改。该方法简单实现了"if cached, return; otherwise create, cache and return"语义。

Java代码

Cache<Key,Value> cache =CacheBuildernewBuilder()

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

cacheget(key,newCallable<Value>(){

@Override

publicValue call()throwsAnyException{

return doThingsTheHardWay(key);

}

});

}catch(ExecutionException e){

thrownewOtherException(egetCause());

}

直接插入

使用cacheput(key, value)方法可以将值直接插入到缓存中,但这将会覆盖缓存中已存在的值。通过使用CacheasMap()所导出的ConcurrentMap对象中的方法也可以对缓存进行修改。但是,请注意asMap中的任何方法都不能自动的将数据加载到缓存中。也就是说,asMap中的各方法是在缓存自动加载范围之外来运作。所以,当你使用CacheLoader或Callable来加载缓存时,应该优先使用Cacheget(K, Callable<V>),而不是CacheasMap()putIfAbsent。

缓存回收

残酷的现实是我们可以肯定的说我们没有足够的内存来缓存一切。你必须来决定:什么时候缓存值不再值得保留?Guava提供了三种基本的缓存回收策略:基于容量回收策略,基于时间回收策略,基于引用回收策略。

基于容量回收策略

使用CacheBuildermaximumSize(long)可以设置缓存的最大容量。缓存将会尝试回收最近没有使用,或者没有经常使用的缓存项。警告:缓存可能会在容量达到限制之前执行回收,通常是在缓存大小逼近限制大小时。

另外,如果不同的缓存项有不同的“权重”, 如,缓存项有不同的内存占用,此时你需要使用CacheBuilderweigher(Weigher)指定一个权重计算函数,并使用CacheBuildermaxmumWeight(long)设定总权重。和maximumSize同样需要注意的是缓存也是在逼近总权重的时候进行回收处理。此外,缓存项的权重是在创建时进行计算,此后不再改变。

Java代码

LoadingCache<Key,Graph> graphs =CacheBuildernewBuilder()

maximumWeight(100000)

weigher(

newWeigher<Key,Graph>(){

publicint weigh(Key k,Graph g){

return gvertices()size();

}

})

build(

newCacheLoader<Key,Graph>(){

publicGraph load(Key key){// no checked exception

return createExpensiveGraph(key);

}

});

基于时间回收策略

CacheBuilder为基于时间的回收提供了两种方式:

expireAfterAccess(long, TimeUnit) 当缓存项在指定的时间段内没有被读或写就会被回收。这种回收策略类似于基于容量回收策略;

expireAfterWrite(long, TimeUnit) 当缓存项在指定的时间段内没有更新就会被回收。如果我们认为缓存数据在一段时间后数据不再可用,那么可以使用该种策略。

就如下面的讨论,定时过期回收会在写的过程中周期执行,偶尔也会读的过程中执行。

测试定时回收

测试定时回收其实不需要那么痛苦的,我们不必非得花费2秒来测试一个2秒的过期。在构建缓存时使用Ticker接口,并通过CacheBuilderticker(Ticker)方法指定时间源,这样我们就不用傻乎乎等系统时钟慢慢的走了。

基于引用回收策略

通过键或缓存值的弱引用(weak references),或者缓存值的软引用(soft references),Guava可以将缓存设置为允许垃圾回收。

CacheBuilderweakKeys() 使用弱引用存储键。当没有(强或软)引用到该键时,相应的缓存项将可以被垃圾回收。由于垃圾回收是依赖==进行判断,因此这样会导致整个缓存也会使用==来比较键的相等性,而不是使用equals();

CacheBuilderweakValues() 使用弱引用存储缓存值。当没有(强或软)引用到该缓存项时,将可以被垃圾回收。由于垃圾回收是依赖==进行判断,因此这样会导致整个缓存也会使用==来比较缓存值的相等性,而不是使用equals();

CacheBuildersoftValues() 使用软引用存储缓存值。当响应需要时,软引用才会被垃圾回收通过最少使用原则回收掉。由于使用软引用造成性能上的影响,我们强烈建议使用可被预言的maximum cache size的策略来代替。同样使用softValues()缓存值的比较也是使用==,而不是equals()。

显示移除

在任何时候,你都可以可以通过下面的方法显式将无效的缓存移除,而不是被动等待被回收:

使用Cacheinvalidate(key)单个移除;

使用CacheinvalidteAll(keys)批量移除;

使用CacheinvalidateAll()移除全部。

谷歌的本地缓存。

使用方法如下:

package comtaobaojumycommonbizmanagerimpl;

import javautilconcurrentCallable;

import javautilconcurrentExecutionException;

import javautilconcurrentTimeUnit;

import comgooglecommoncacheCache;

import comgooglecommoncacheCacheBuilder;

import comgooglecommoncacheCacheLoader;

import comgooglecommoncacheLoadingCache;

public class LoadingCacheDemo {

public static void main(String[] args) throws ExecutionException {

//方法一

//创建本地缓存,当本地缓存不命中时,调用load方法,返回结果,再缓存结果。

LoadingCache loadingCache = CacheBuildernewBuilder()expireAfterWrite(10, TimeUnitSECONDS)maximumSize(10000)build(new CacheLoader() {

@Override

public String load(String key) throws Exception {

return getString(key);

}

});

loadingCacheget("test");

loadingCacheget("test2");

//方法二

//创建缓存对像

Cache cache = CacheBuildernewBuilder()expireAfterWrite(10, TimeUnitSECONDS)maximumSize(10000)build();

//调用缓存中的get方法,当缓存命中时直接返回结果,当不命中时,通过给定的Callable类call方法 返回结果,再缓存。这个方法

//到更灵活,可以用一个cache对象缓存多种不同的数据,只要用不同的Callable对象就行。

cacheget("111", new Callable() {

@Override

public String call() throws Exception {

Systemoutprintln("经过 call()");

return "value";

}

});

cacheget("222", new Callable() {

@Override

public String call() throws Exception {

Systemoutprintln("经过 call()");

return "value";

}

});

}

public static String getString(String key) {

Systemoutprintln("经过 getString()");

return key + "--Test";

}

}

首先,看一下使用范例:

Java代码

LoadingCache<Key,Graph> graphs =CacheBuildernewBuilder()

maximumSize(1000)

expireAfterWrite(10,TimeUnitMINUTES)

removalListener(MY_LISTENER)

build(

newCacheLoader<Key,Graph>(){

publicGraph load(Key key)throwsAnyException{

return createExpensiveGraph(key);

}

});

适用性

缓存在很多情况下都是非常有用的。比如,我们需要多次根据给定的输入获取值,而且该值计算或者获取的开销是非常昂贵的。

缓存和ConcurrentMap是非常相像的,但是它们也不完全一样。最根本的区别就是,ConcurrentMap会持有所有添加的对象,直到被显示的移除。而缓存为了限制其内存的使用,通常都会配置成可以自动的将对象移除。在某些情况下即使不自动移除对象也是非常有用的,如LoadingCache它会自动加载缓存对象。

一般,Guava缓存适用于以下几种情况:

你愿意花费一些内存来换取性能提升;

你预测到某些键会多次进行查询;

你的缓存数据不超过内存(Guava缓存是单个应用中的本地缓存。它不会将数据存储到文件中,或者外部服务器。如果不适合你,可以考虑一下 Memcached)。

如果你的需要符合上面所说的每一条,那么选择Guava缓存绝对没错。

使用CacheBuilder的构建模式可以获取一个Cache,如上面的范例所示。但是如何进行定制才是比较有趣的。

注意:如果你不需要缓存的这些特性,那么使用ConcurrentHashMap会有更好的内存效率,但是如果想基于旧有的ConcurrentMap复制实现Cache的一些特性,那么可能是非常困难或者根本不可能。

加载

对于缓存首先需要明确的是:有没有一个方法可以通过给定的键来计算/加载相应的值?如果有,那么可以使用CacheLoader。如果没有这样的方法,或者你想复写缓存的加载方式,但你仍想保留“get-if-absent-compute”语义,你可以在调用get方法时传入一个Callable实例,来达到目的。缓存的对象可以通过Cacheput直接插入,但是自动加载是首选,因为自动加载可以更加容易的判断所有缓存信息的一致性。

From a CacheLoader

LoadingCache 缓存是通过一个CacheLoader来构建缓存。创建一个CacheLoader仅需要实现V load(K key) throws Exception方法即可。下面的范例就是如何创建一个LoadingCache:

Java代码

LoadingCache<Key,Graph> graphs =CacheBuildernewBuilder()

maximumSize(1000)

build(

newCacheLoader<Key,Graph>(){

publicGraph load(Key key)throwsAnyException{

return createExpensiveGraph(key);

}

});

try{

return graphsget(key);

}catch(ExecutionException e){

thrownewOtherException(egetCause());

}

通过方法get(K)可以对LoadingCache进行查询。该方法要不返回已缓存的值,要不通过CacheLoader来自动加载相应的值到缓存中。这里需要注意的是:CacheLoader可能会抛出Exception,LoaderCacheget(K)则可能会抛出ExecutionException。假如你定义的CacheLoader没有声明检查型异常,那么可以通过调用getUnchecked(K)来获取缓存值;但是一旦当CacheLoader中声明了检查型异常,则不可以调用getUnchecked。

Java代码

LoadingCache<Key,Graph> graphs =CacheBuildernewBuilder()

expireAfterAccess(10,TimeUnitMINUTES)

build(

newCacheLoader<Key,Graph>(){

publicGraph load(Key key){// no checked exception

return createExpensiveGraph(key);

}

});

return graphsgetUnchecked(key);

批量查询可以使用getAll(Iterable< extends K>)方法。缺省,getAll方法将循环每一个键调用CacheLoaderload方法获取缓存值。当缓存对象的批量获取比单独获取更有效时,可以通过复写CacheLoaderloadAll方法实现缓存对象的加载。此时当调用getAll(Iterable)方法时性能也会提升。

需要注意的是CacheLoaderloadAll的实现可以为没有明确要求的键加载缓存值。比如,当为某组中的一些键进行计算时,loadAll方法则可能会同时加载组中其余键的值。

From a Callable

所有Guava缓存,不论是否会自动加载,都支持get(K, Callable(V))方法。当给定键的缓存值已存在时则直接返回,否则通过指定的Callable方法进行计算并将值存放到缓存中。直到加载完成时,相应的缓存才会被更改。该方法简单实现了"if cached, return; otherwise create, cache and return"语义。

Java代码

Cache<Key,Value> cache =CacheBuildernewBuilder()

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

cacheget(key,newCallable<Value>(){

@Override

publicValue call()throwsAnyException{

return doThingsTheHardWay(key);

}

});

}catch(ExecutionException e){

thrownewOtherException(egetCause());

}

直接插入

使用cacheput(key, value)方法可以将值直接插入到缓存中,但这将会覆盖缓存中已存在的值。通过使用CacheasMap()所导出的ConcurrentMap对象中的方法也可以对缓存进行修改。但是,请注意asMap中的任何方法都不能自动的将数据加载到缓存中。也就是说,asMap中的各方法是在缓存自动加载范围之外来运作。所以,当你使用CacheLoader或Callable来加载缓存时,应该优先使用Cacheget(K, Callable<V>),而不是CacheasMap()putIfAbsent。

缓存回收

残酷的现实是我们可以肯定的说我们没有足够的内存来缓存一切。你必须来决定:什么时候缓存值不再值得保留?Guava提供了三种基本的缓存回收策略:基于容量回收策略,基于时间回收策略,基于引用回收策略。

基于容量回收策略

使用CacheBuildermaximumSize(long)可以设置缓存的最大容量。缓存将会尝试回收最近没有使用,或者没有经常使用的缓存项。警告:缓存可能会在容量达到限制之前执行回收,通常是在缓存大小逼近限制大小时。

另外,如果不同的缓存项有不同的“权重”, 如,缓存项有不同的内存占用,此时你需要使用CacheBuilderweigher(Weigher)指定一个权重计算函数,并使用CacheBuildermaxmumWeight(long)设定总权重。和maximumSize同样需要注意的是缓存也是在逼近总权重的时候进行回收处理。此外,缓存项的权重是在创建时进行计算,此后不再改变。

Java代码

LoadingCache<Key,Graph> graphs =CacheBuildernewBuilder()

maximumWeight(100000)

weigher(

newWeigher<Key,Graph>(){

publicint weigh(Key k,Graph g){

return gvertices()size();

}

})

build(

newCacheLoader<Key,Graph>(){

publicGraph load(Key key){// no checked exception

return createExpensiveGraph(key);

}

});

基于时间回收策略

CacheBuilder为基于时间的回收提供了两种方式:

expireAfterAccess(long, TimeUnit) 当缓存项在指定的时间段内没有被读或写就会被回收。这种回收策略类似于基于容量回收策略;

expireAfterWrite(long, TimeUnit) 当缓存项在指定的时间段内没有更新就会被回收。如果我们认为缓存数据在一段时间后数据不再可用,那么可以使用该种策略。

就如下面的讨论,定时过期回收会在写的过程中周期执行,偶尔也会读的过程中执行。

测试定时回收

测试定时回收其实不需要那么痛苦的,我们不必非得花费2秒来测试一个2秒的过期。在构建缓存时使用Ticker接口,并通过CacheBuilderticker(Ticker)方法指定时间源,这样我们就不用傻乎乎等系统时钟慢慢的走了。

基于引用回收策略

通过键或缓存值的弱引用(weak references),或者缓存值的软引用(soft references),Guava可以将缓存设置为允许垃圾回收。

CacheBuilderweakKeys() 使用弱引用存储键。当没有(强或软)引用到该键时,相应的缓存项将可以被垃圾回收。由于垃圾回收是依赖==进行判断,因此这样会导致整个缓存也会使用==来比较键的相等性,而不是使用equals();

CacheBuilderweakValues() 使用弱引用存储缓存值。当没有(强或软)引用到该缓存项时,将可以被垃圾回收。由于垃圾回收是依赖==进行判断,因此这样会导致整个缓存也会使用==来比较缓存值的相等性,而不是使用equals();

CacheBuildersoftValues() 使用软引用存储缓存值。当响应需要时,软引用才会被垃圾回收通过最少使用原则回收掉。由于使用软引用造成性能上的影响,我们强烈建议使用可被预言的maximum cache size的策略来代替。同样使用softValues()缓存值的比较也是使用==,而不是equals()。

显示移除

在任何时候,你都可以可以通过下面的方法显式将无效的缓存移除,而不是被动等待被回收:

使用Cacheinvalidate(key)单个移除;

使用CacheinvalidteAll(keys)批量移除;

使用CacheinvalidateAll()移除全部。

以上就是关于如何使用Guava的缓存管理全部的内容,包括:如何使用Guava的缓存管理、google common cache有什么用、java google 的内存缓存为什么总调用cacheloader等相关内容解答,如果想了解更多相关内容,可以关注我们,你们的支持是我们更新的动力!

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/web/9752089.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2023-05-01
下一篇 2023-05-01

发表评论

登录后才能评论

评论列表(0条)

保存