代码之家  ›  专栏  ›  技术社区  ›  Bojan Vukasovic

咖啡因缓存-单个值的多个键

  •  0
  • Bojan Vukasovic  · 技术社区  · 7 年前

    我有一个带有键的咖啡因缓存->价值映射。有多种实现 Key equals 方法。为了根据从缓存中删除值 someOtherVal ,我必须使用这样的代码 cache.asMap().keySet().removeIf(comp::isSame) 超慢的。

    对于缓存中这种多键到单值的映射,还有其他解决方案吗?我想到的一件事是有两个缓存实例,一个是 Cache<Key, String> 其他的有 Cache<someOtherVal, Key> ,并且每当我想删除一个值时,我就使用另一个缓存来定位键。

    import java.time.Duration;
    import java.util.Objects;
    import java.util.UUID;
    
    import com.github.benmanes.caffeine.cache.Cache;
    import com.github.benmanes.caffeine.cache.Caffeine;
    import com.google.common.base.Stopwatch;
    
    public class Removal {
        private static final int MAX = 1_000_000;
    
        interface Key{
            String getSomeOtherVal();
            default boolean isSame(Key k){
                return Objects.equals(k.getSomeOtherVal(),getSomeOtherVal());
            }
        }
    
        static class KeyImpl implements Key{
            int id;
            String someOtherVal;
    
            public KeyImpl(int id, String someOtherVal) {
                this.id = id;
                this.someOtherVal = someOtherVal;
            }
    
            public int getId() {
                return id;
            }
    
            @Override
            public String getSomeOtherVal() {
                return someOtherVal;
            }
    
            @Override
            public boolean equals(Object o) {
                if (this == o)
                    return true;
                if (o == null || getClass() != o.getClass())
                    return false;
                KeyImpl key = (KeyImpl)o;
                return id == key.id;
            }
    
            @Override
            public int hashCode() {
                return Objects.hash(id);
            }
        }
    
        Cache<Key, String> cache = Caffeine.newBuilder().build();
    
        public static void main(String[] args) {
            Removal s = new Removal();
            s.fill();
            Duration sRem = s.slowRemovalFirst100();
            Duration fRem = s.fastRemoval100To200();
            System.out.println("Slow removal in " + sRem);
            System.out.println("Fast removal in " + fRem);
        }
    
        private Duration slowRemovalFirst100(){
            Stopwatch sw = Stopwatch.createStarted();
            for(int i=0; i<100; i++){
                Key comp = new KeyImpl(i, String.valueOf(i));
                cache.asMap().keySet().removeIf(comp::isSame);  //Finds a key by some other property and then removes it (SLOW)
                //System.out.println("Removed " + i);
            }
            return sw.stop().elapsed();
        }
    
        private Duration fastRemoval100To200(){
            Stopwatch sw = Stopwatch.createStarted();
            for(int i=100; i<200; i++){
                Key comp = new KeyImpl(i, String.valueOf(i));
                cache.invalidate(comp); //Uses direct access to map by key (FAST)
                //System.out.println("Removed " + i);
            }
            return sw.stop().elapsed();
        }
    
        private void fill(){
            for(int i=0; i<MAX; i++){
                cache.put(new KeyImpl(i, String.valueOf(i)), UUID.randomUUID().toString());
            }
        }
    }
    

    在我的计算机上运行此代码的结果:

    Slow removal in PT2.807105177S
    Fast removal in PT0.000126183S
    

    在那里你可以看到如此巨大的差异。。。

    1 回复  |  直到 7 年前
        1
  •  3
  •   Bojan Vukasovic    7 年前

    好吧,我设法解决了这个问题:

    public class IndexedCache<K,V> implements Cache<K,V> {
    
        @Delegate
        private Cache<K, V> cache;
        private Map<Class<?>, Map<Object, Set<K>>> indexes;
    
        private IndexedCache(Builder<K, V> bldr){
            this.indexes = bldr.indexes;
            cache = bldr.caf.build();
        }
    
        public <R> void invalidateAllWithIndex(Class<R> clazz, R value) {
            cache.invalidateAll(indexes.get(clazz).getOrDefault(value, new HashSet<>()));
        }
    
        public static class Builder<K, V>{
            Map<Class<?>, Function<K, ?>> functions = new HashMap<>();
            Map<Class<?>, Map<Object, Set<K>>> indexes = new ConcurrentHashMap<>();
            Caffeine<K,V> caf;
    
            public <R> Builder<K,V> withIndex(Class<R> clazz, Function<K, R> function){
                functions.put(clazz, function);
                indexes.put(clazz, new ConcurrentHashMap<>());
                return this;
            }
    
            public IndexedCache<K, V> buildFromCaffeine(Caffeine<Object, Object> caffeine) {
                caf = caffeine.writer(new CacheWriter<K, V>() {
    
                    @Override
                    public void write( K k, V v) {
                        for(Map.Entry<Class<?>, Map<Object, Set<K>>> indexesEntry : indexes.entrySet()){
                            indexesEntry.getValue().computeIfAbsent(functions.get(indexesEntry.getKey()).apply(k), (ky)-> new HashSet<>())
                            .add(k);
                        }
                    }
    
                    @Override
                    public void delete( K k,  V v,  RemovalCause removalCause) {
                        for(Map.Entry<Class<?>, Map<Object, Set<K>>> indexesEntry : indexes.entrySet()){
                            indexesEntry.getValue().remove(functions.get(indexesEntry.getKey()).apply(k));
                        }
                    }
                });
                return new IndexedCache<>(this);
            }
        }
    
    }
    

    @AllArgsConstructor
        @Data
        @EqualsAndHashCode(onlyExplicitlyIncluded = true)
        static class CompositeKey{
            @EqualsAndHashCode.Include
            Integer k1;
            String k2;
            Long k3;
        }
    
    
        public static void main(String[] args) {
    
    
            Caffeine<Object, Object> cfein = Caffeine.newBuilder().softValues().maximumSize(200_000);
    
            IndexedCache<CompositeKey, String> cache = new IndexedCache.Builder<CompositeKey, String>()
                    .withIndex(Long.class, ck -> ck.getK3())
                    .withIndex(String.class, ck -> ck.getK2())
                    .buildFromCaffeine(cfein);
    
    
            for(int i=0; i<100; i++){
                cache.put(new CompositeKey(i, String.valueOf(i), Long.valueOf(i)), "sdfsdf");
            }
    
    
            for(int i=0; i<10; i++){
                //use equals method of CompositeKey to do equals comp.
                cache.invalidate(new CompositeKey(i, String.valueOf(i), Long.valueOf(i)));
            }
    
            for(int i=10; i<20; i++){
                //use Long index
                cache.invalidateAllWithIndex(Long.class, Long.valueOf(i));
            }
    
            for(int i=20; i<30; i++){
                //use String index
                cache.invalidateAllWithIndex(String.class, String.valueOf(i));
            }
    
    
            int y = 4;
    
        }
    

    以下是我的讨论链接: https://github.com/ben-manes/caffeine/issues/279