java:基于guava缓存(LoadingCache)实现结果缓存避免重复计算
Posted 10km
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了java:基于guava缓存(LoadingCache)实现结果缓存避免重复计算相关的知识,希望对你有一定的参考价值。
在项目设计中,我们经常会遇到这样一种场景,输入一个参数,进行运算返回一个值,输入值和返回值是固定的映射关系。
比如我想通过反射判断一个方法(Method)的最后一个参数名是否是为指定的格式:
实现方法如下:
public class ServiceCommonTools
/**
* 最后一个参数类型为String,且参数名为(tokenid|tid|tkid),或有TokenId注解,
* 则返回参数的名字,否则返回空字符串
* @param method
*/
public static String lastArgNameIfTokenId0(Method method)
if(null != method)
/**
* 判断最后一个参数是否为String类型,
* 且参数名为:(tokenid|tid|tkid)不区分大小写,或有TokenId注解
*/
String[] names = ARGNAMES.get(method);
if(names.length > 0)
int lastIndex = names.length - 1;
String last = names[lastIndex];
if(String.class.isAssignableFrom(method.getParameterTypes()[lastIndex]))
/** 参数名为(tokenid|tid|tkid)[不区分大小写] */
Pattern pattern = Pattern.compile("(tokenid|tid|tkid)",Pattern.CASE_INSENSITIVE);
/** 参数名配置 */
if(pattern.matcher(last).matches())
return last;
else if (null != method.getParameters()[lastIndex].getAnnotation(TokenId.class))
/** 如果有TokenId 注解 */
return last;
return "";
这个计算逻辑代码看着挺长的,挺复杂的,但无非就是对一个输入的Method对象返回一个String。
Java程序在运行时每一个Method对象都是一个常量,所以每次调用这个方法的时候对于同一个Method返回的字符串也是必定是同一个字符串。
如果这个方法系统中会被反复调用,每次都对同一个Method要再进行一相重复的计算对系统的运行效率来说是很不划算的。如果对同一个Method对象只计算一次,下次直接返回第一次计算的结果,是不是就可以提高系统的运行效率?
这种情况我遇到过多次,我决定解决这个效率隐患问题,
guava的缓存(com.google.common.cache.LoadingCache
)可以实现当读取缓中不存在Key时就进行计算,以后再读取指定的Key时直接返回缓存结果。这正是我想要的效果。
所以我设计了如下一个泛型类FunctionCached<K,V>
,基于guava的缓存(LoadingCache)工具将计算结果缓存,避免重复计算:
import com.google.common.base.Function;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkArgument;
/**
* 基于GUAVA @link LoadingCache实现类型转转换接口@link Function,
* 当一个输入K的计算结果已经存在于缓存时,直接返回,无需再次计算,
* 避免对同一个输入数据多次重复计算。
* @author guyadong
*
* @param <K> 输入参数类型
* @param <V> 输出参数类型
*/
public class FunctionCached<K,V> implements Function<K, V>
private final LoadingCache<K, V> cache;
private final Function<K, V> getterFunction;
private final V defaultValue;
/**
* 构造方法
* @param getterFunction 原类型转换接口实现,当缓存中不存在结果时调用此接口进行计算
* @param defaultValue K为@code null时返回的默认值
*/
public FunctionCached(final Function<K, V> getterFunction,V defaultValue)
this.getterFunction = checkNotNull(getterFunction,"getterFunction is null");
/** 输入参数不可以是缓存实例 */
checkArgument(!(getterFunction instanceof FunctionCached),"getterFunction must not be instance of %s",getClass().getName());
this.defaultValue = defaultValue;
this.cache = CacheBuilder.newBuilder().build(new CacheLoader<K, V>()
@Override
public V load(K key) throws Exception
return getterFunction.apply(key);
);
/**
* 构造方法,
* K 为@code null时返回@code null
* @param getterFunction 原类型转换接口实现,当缓存中不存在结果时调用此接口进行计算
*/
public FunctionCached(Function<K, V> getterFunction)
this(getterFunction, null);
/**
* 非缓存调用
* @param key
* @return @code key为@code null返回@link #defaultValue
*/
public V getUncached(K key)
return null == key ? defaultValue : getterFunction.apply(key);
/**
* 缓存调用
* @param key
* @return @code key为@code null返回@link #defaultValue
*/
public V get(K key)
if(null != key)
return cache.getUnchecked(key);
return defaultValue;
/**
* 缓存调用
* @see #get(Object)
* @see com.google.common.base.Function#apply(java.lang.Object)
*/
@Override
public V apply(K input)
return get(input);
/**
* 返回@code getterFunction的@link FunctionCached实例,
* 如果@code getterFunction为@link FunctionCached实例,则直接返回,否则创建新实例
* @param getterFunction
* @param defaultValue
* @see #FunctionCached(Function, Object)
*/
public static <K, V> FunctionCached<K, V> of(Function<K, V> getterFunction,V defaultValue)
if(getterFunction instanceof FunctionCached)
return (FunctionCached<K, V>) getterFunction;
return new FunctionCached<K, V>(getterFunction, defaultValue);
/**
* 返回@code getterFunction的@link FunctionCached实例(默认实例为@code null)
* @param getterFunction
* @see #of(Function, Object)
*/
public static <K, V> FunctionCached<K, V> of(Function<K, V> getterFunction)
return of(getterFunction,null);
有了FunctionCached<K,V>
,对于前面那个计算方法参数名的例子,我们可以做如下改进:
/**
* 最后一个参数类型为String,且参数名为(tokenid|tid|tkid)则返回参数的名字,否则返回空字符串,
*/
public static final FunctionCached<Method,String> LAST_ARGNAME_IF_TOKENID
= FunctionCached.of(ServiceCommonTools::lastArgNameIfTokenId0);
就是定义一个类型为FunctionCached<Method,String>
的LAST_ARGNAME_IF_TOKENID
的常量对象用于保存计算结果。将lastArgNameIfTokenId0
方法以lamda表达式的方式传递给FunctionCached作为计算方法。
这样,当调用 FunctionCached.get()
方法且缓存中不存在指定的Method时就会自动调用lastArgNameIfTokenId0
方法来计算,如果存在则直接返回缓存的值。每一个Method对象只会被计算一次。
原本需要调用lastArgNameIfTokenId0
的地方,可以直接替换为LAST_ARGNAME_IF_TOKENID.get (method)
FunctionCached
是泛型类可以适用任何输入输出为固定映射的常量计算的场景
FunctionCached<K,V>
类的完整代码参见我的码云仓库:
https://gitee.com/l0km/common-java/blob/master/common-base2/src/main/java/net/gdface/utils/FunctionCached.java
以上是关于java:基于guava缓存(LoadingCache)实现结果缓存避免重复计算的主要内容,如果未能解决你的问题,请参考以下文章