guava是google的一个开源的基础java库,其中提供了一个非常有用的缓存(cache)功能。创建cache的过程大概如下:
LoadingCache<Key, Graph> graphs = CacheBuilder.newBuilder()
.maximumSize(1000)
.expireAfterWrite(10, TimeUnit.MINUTES)
.removalListener(MY_LISTENER)
.build(
new CacheLoader<Key, Graph>() {
@Override
public Graph load(Key key) throws AnyException {
return createExpensiveGraph(key);
}
});
本文的目标不是介绍如何使用guava 的cache,我们知道guava 的cache是基于ConcurrentMap来实现的,但我们也知道Map中不能使用数组(Object[],int[]…)作为key,所以在创建Cache对象时,自然也不能使用数组作为Key。如果希望把一组对象作为Key,可以考虑用把数组封装为List作为Key.
最近在我的一个项目,出于效率考虑,我就是希望用Object[]作为Key.能不能实现呢?
要解决这个问题,首先要知道为什么不能用数组做Map的key。数组的equals方法只是简单比较两个数组指针是否一样,并不比较数组中的元素,所以不能正确判断两个数组相等,hashCode方法则只是根据对象指针的地址计算,所以数组类型的equals和hashCode方法的计算结果不能作为Map识别Key的依据。
所以只要对数组对象能正确计算hash code,正确比较相等,Map也是可以用数组做key的。
仔细研究com.google.common.cache.LocalCache
的源码,可以知道,LocalCache是使用Equivalence对象实现对象比较和哈希码计算的,参见com.google.common.cache.LocalCache.Segment.getEntry(Object key, int hash)
,代码如下,keyEquivalence
就是用于Key等价计算的Equivalence对象:
ReferenceEntry<K, V> getEntry(Object key, int hash) {
for (ReferenceEntry<K, V> e = getFirst(hash); e != null; e = e.getNext()) {
if (e.getHash() != hash) {
continue;
}
K entryKey = e.getKey();
if (entryKey == null) {
tryDrainReferenceQueues();
continue;
}
if (map.keyEquivalence.equivalent(key, entryKey)) {
return e;
}
}
return null;
}
进一步研究com.google.common.cache.CacheBuilder
的代码,找到了如下代码,哈,原来CacheBuilder可以指定Equivalence,如果不指定就使用默认值:
/**
* Sets a custom {@code Equivalence} strategy for comparing keys.
*
* <p>By default, the cache uses {@link Equivalence#identity} to determine key equality when
* {@link #weakKeys} is specified, and {@link Equivalence#equals()} otherwise.
*
* @return this {@code CacheBuilder} instance (for chaining)
*/
@GwtIncompatible // To be supported
CacheBuilder<K, V> keyEquivalence(Equivalence<Object> equivalence) {
checkState(keyEquivalence == null, "key equivalence was already set to %s", keyEquivalence);
keyEquivalence = checkNotNull(equivalence);
return this;
}
所以只要我们实现实现一个Equivalence对象,通过上面这个CacheBuilder.keyEquivalence(Equivalence<Object> equivalence)
方法传递给LocalCache,就可以用数组做key了。
但是CacheBuilder.keyEquivalence(Equivalence<Object> equivalence)
方法的访问修饰符不是public,所以无法在外部访问,解决这个并不难,如下在com.google.common.cache
包下创建一个类就调用CacheBuilder.keyEquivalence(Equivalence<Object> equivalence)
方法就可以了。
所以完整的实现代码如下:
package com.google.common.cache;
import java.util.Arrays;
import java.util.Objects;
import com.google.common.base.Equivalence;
public class DeepCacheBuilder {
private static final Equivalence<Object> DEEP_EQUIVALENCE = new Equivalence<Object>(){
@Override
protected boolean doEquivalent(Object a, Object b) {
return Objects.deepEquals(a, b);
}
@Override
protected int doHash(Object object) {
return deepHashCode(object);
}};
public static final int deepHashCode(Object a){
if (a == null){
return 0;
}else if (a instanceof Object[]) {
return Arrays.deepHashCode((Object[]) a);
} else if (a instanceof byte[]) {
return Arrays.hashCode((byte[]) a);
} else if (a instanceof short[]) {
return Arrays.hashCode((short[]) a);
} else if (a instanceof int[]) {
return Arrays.hashCode((int[]) a);
} else if (a instanceof long[]) {
return Arrays.hashCode((long[]) a);
} else if (a instanceof char[]) {
return Arrays.hashCode((char[]) a);
} else if (a instanceof float[]) {
return Arrays.hashCode((float[]) a);
} else if (a instanceof double[]) {
return Arrays.hashCode((double[]) a);
} else if (a instanceof boolean[]) {
return Arrays.hashCode((boolean[]) a);
}
return a.hashCode();
}
public static final CacheBuilder<Object, Object> newBuilder(){
return newBuilder(DEEP_EQUIVALENCE,DEEP_EQUIVALENCE);
}
public static final CacheBuilder<Object, Object> newBuilder(Equivalence<Object> keyEquivalence,Equivalence<Object> valueEquivalence){
CacheBuilder<Object, Object> builder = CacheBuilder.newBuilder();
if(keyEquivalence != null){
builder.keyEquivalence(keyEquivalence);
}
if(valueEquivalence != null){
builder.valueEquivalence(valueEquivalence);
}
return builder;
}
}