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)实现结果缓存避免重复计算的主要内容,如果未能解决你的问题,请参考以下文章

基于guava实现本地缓存

Java内存缓存-通过Google Guava创建缓存

Guava缓存基础

Guava的使用

Java内存缓存工具实现 - Guava LoadingCache

java缓存redis缓存guava缓存java中实现缓存的几种方式