java:基于guava缓存(LoadingCache)实现结果缓存避免重复计算

文章介绍了如何使用Guava的LoadingCache来优化对固定输入产生固定输出的计算场景,例如检查Method对象的最后一个参数是否符合特定规则。通过创建一个泛型类FunctionCached,实现了计算结果的缓存,避免了对同一输入的重复计算,提高了系统运行效率。文章提供了一个具体的例子,展示了如何将该功能应用于方法参数名的检查,并提供了相关代码实现。
摘要由CSDN通过智能技术生成

在项目设计中,我们经常会遇到这样一种场景,输入一个参数,进行运算返回一个值,输入值和返回值是固定的映射关系。
比如我想通过反射判断一个方法(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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

10km

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值