Retrofit 风格的 RxCache及其多种缓存替换算法

640?wx_fmt=jpeg

RxCache 是一个支持 Java 和 Android 的 Local Cache 。

之前的文章《给 Java 和 Android 构建一个简单的响应式Local Cache》《RxCache 整合 Android 的持久层框架 greenDAO、Room》曾详细介绍过它。

目前,对框架增加一些 Annotation 以及 Cache 替换算法。

一. 基于 Annotation 完成缓存操作

类似 Retrofit 风格的方式,支持通过标注 Annotation 来完成缓存的操作。

例如先定义一个接口,用于定义缓存的各种操作。

 
 
  1. public interface Provider {


  2.    @CacheKey("user")

  3.    @CacheMethod(methodType = MethodType.GET)

  4.    <T> Record<T> getData(@CacheClass Class<T> clazz);


  5.    @CacheKey("user")

  6.    @CacheMethod(methodType = MethodType.SAVE)

  7.    @CacheLifecycle(duration = 2000)

  8.    void putData(@CacheValue User user);


  9.    @CacheKey("user")

  10.    @CacheMethod(methodType = MethodType.REMOVE)

  11.    void removeUser();


  12.    @CacheKey("test")

  13.    @CacheMethod(methodType = MethodType.GET, observableType = ObservableType.MAYBE)

  14.    <T> Maybe<Record<T>> getMaybe(@CacheClass Class<T> clazz);

  15. }

通过 CacheProvider 创建该接口,然后可以完成各种缓存操作。

 
 
  1. public class TestCacheProvider {

  2.    public static void main(String[] args) {


  3.        RxCache.config(new RxCache.Builder());

  4.        RxCache rxCache = RxCache.getRxCache();


  5.        CacheProvider cacheProvider = new CacheProvider.Builder().rxCache(rxCache).build();

  6.        Provider provider = cacheProvider.create(Provider.class);

  7.        

  8.        User u = new User();

  9.        u.name = "tony";

  10.        u.password = "123456";

  11.        provider.putData(u); // 将u存入缓存中


  12.        Record<User> record = provider.getData(User.class); // 从缓存中获取key="user"的数据

  13.        if (record!=null) {

  14.            System.out.println(record.getData().name);

  15.        }


  16.        provider.removeUser(); // 从缓存中删除key="user"的数据

  17.        record = provider.getData(User.class);

  18.        if (record==null) {

  19.            System.out.println("record is null");

  20.        }



  1.        User u2 = new User();

  2.        u2.name = "tony2";

  3.        u2.password = "000000";

  4.        rxCache.save("test",u2);


  5.        Maybe<Record<User>> maybe = provider.getMaybe(User.class); // 从缓存中获取key="test"的数据,返回的类型为Maybe

  6.        maybe.subscribe(new Consumer<Record<User>>() {

  7.            @Override

  8.            public void accept(Record<User> userRecord) throws Exception {

  9.                User user = userRecord.getData();

  10.                if (user!=null) {

  11.                    System.out.println(user.name);

  12.                    System.out.println(user.password);

  13.                }

  14.            }

  15.        });

  16.    }

  17. }

CacheProvider 核心是 create(),它通过动态代理来创建Provider。

 
 
  1.    public <T> T create(Class<T> clazz) {

  2.        CacheProxy cacheProxy = new CacheProxy(rxCache);

  3.        try {

  4.            return (T) Proxy.newProxyInstance(CacheProvider.class.getClassLoader(), new Class[]{clazz}, cacheProxy);

  5.        } catch (Exception e) {

  6.            e.printStackTrace();

  7.        }

  8.        return null;

  9.    }

其中,CacheProxy 实现了 InvocationHandler 接口,是创建代理类的调用处理器。

 
 
  1. package com.safframework.rxcache.proxy;

  2. import com.safframework.rxcache.RxCache;

  3. import com.safframework.rxcache.proxy.annotation.*;

  4. import java.lang.annotation.Annotation;

  5. import java.lang.reflect.InvocationHandler;

  6. import java.lang.reflect.Method;

  7. /**

  8. * Created by tony on 2018/10/30.

  9. */

  10. public class CacheProxy implements InvocationHandler {

  11.    RxCache rxCache;


  12.    public CacheProxy(RxCache rxCache) {

  13.        this.rxCache = rxCache;

  14.    }


  15.    @Override

  16.    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

  17.        CacheMethod cacheMethod = method.getAnnotation(CacheMethod.class);

  18.        CacheKey cacheKey = method.getAnnotation(CacheKey.class);

  19.        CacheLifecycle cacheLifecycle = method.getAnnotation(CacheLifecycle.class);

  20.        Annotation[][] allParamsAnnotations = method.getParameterAnnotations();

  21.        

  22.        Class cacheClazz = null;

  23.        Object cacheValue = null;


  24.        if (allParamsAnnotations != null) {

  25.            for (int i = 0; i < allParamsAnnotations.length; i++) {

  26.                Annotation[] paramAnnotations = allParamsAnnotations[i];

  27.                if (paramAnnotations != null) {

  28.                    for (Annotation annotation : paramAnnotations) {

  29.                        if (annotation instanceof CacheClass) {

  30.                            cacheClazz = (Class) args[i];

  31.                        }

  32.                        if (annotation instanceof CacheValue) {

  33.                            cacheValue = args[i];

  34.                        }

  35.                    }

  36.                }

  37.            }

  38.        }


  39.        if (cacheMethod!=null) {

  40.            MethodType methodType = cacheMethod.methodType();

  41.            long duration = -1;

  42.            if (cacheLifecycle != null) {

  43.                duration = cacheLifecycle.duration();

  44.            }

  45.            if (methodType == MethodType.GET) {

  46.                ObservableType observableType = cacheMethod.observableType();

  47.                if (observableType==ObservableType.NOUSE) {

  48.                    return  rxCache.get(cacheKey.value(),cacheClazz);

  49.                } else if (observableType == ObservableType.OBSERVABLE){

  50.                    return  rxCache.load2Observable(cacheKey.value(),cacheClazz);

  51.                } else if (observableType==ObservableType.FLOWABLE) {

  52.                    return  rxCache.load2Flowable(cacheKey.value(),cacheClazz);

  53.                } else if (observableType==ObservableType.SINGLE) {

  54.                    return  rxCache.load2Single(cacheKey.value(),cacheClazz);

  55.                } else if (observableType==ObservableType.MAYBE) {

  56.                    return  rxCache.load2Maybe(cacheKey.value(),cacheClazz);

  57.                }

  58.            } else if (methodType == MethodType.SAVE) {

  59.                rxCache.save(cacheKey.value(),cacheValue,duration);

  60.            } else if (methodType == MethodType.REMOVE) {

  61.                rxCache.remove(cacheKey.value());

  62.            }

  63.        }

  64.        return null;

  65.    }

  66. }

CacheProxy 的 invoke() 方法先获取 Method 所使用的 Annotation,包括CacheMethod、CacheKey、CacheLifecycle。

其中,CacheMethod 是最核心的 Annotation,它取决于 rxCache 使用哪个方法。CacheMethod 支持的方法类型包括:获取、保存、删除缓存。当 CacheMethod 的 methodType 是 GET 类型,则可能会返回 RxJava 的各种 Observable 类型,或者还是返回所存储的对象类型。

CacheKey 是任何方法都需要使用的 Annotation。CacheLifecycle 只有保存缓存时才会使用。

二. 支持多种缓存替换算法

RxCache 包含了两级缓存: Memory 和 Persistence 。

Memory 的默认实现 FIFOMemoryImpl、LRUMemoryImpl、LFUMemoryImpl 分别使用 FIFO、LRU、LFU 算法来缓存数据。

2.1 FIFO

通过使用 LinkedList 存放缓存的 keys,ConcurrentHashMap 存放缓存的数据,就可以实现 FIFO。

2.2 LRU

LRU是Least Recently Used的缩写,即最近最少使用,常用于页面置换算法,是为虚拟页式存储管理服务的。

使用 ConcurrentHashMap 和 ConcurrentLinkedQueue 实现该算法。如果某个数据已经存放在缓存中,则从 queue 中删除并添加到 queue 的第一个位置。如果缓存已满,则从 queue 中删除最后面的数据。并把新的数据添加到缓存。

 
 
  1. public class LRUCache<K,V> {

  2.    private Map<K,V> cache = null;

  3.    private AbstractQueue<K> queue = null;

  4.    private int size = 0;


  5.    public LRUCache() {

  6.        this(Constant.DEFAULT_CACHE_SIZE);

  7.    }


  8.    public LRUCache(int size) {

  9.        this.size = size;

  10.        cache = new ConcurrentHashMap<K,V>(size);

  11.        queue = new ConcurrentLinkedQueue<K>();

  12.    }


  13.    public boolean containsKey(K key) {

  14.        return cache.containsKey(key);

  15.    }


  16.    public V get(K key) {

  17.        //Recently accessed, hence move it to the tail

  18.        queue.remove(key);

  19.        queue.add(key);

  20.        return cache.get(key);

  21.    }


  22.    public V getSilent(K key) {

  23.        return cache.get(key);

  24.    }


  25.    public void put(K key, V value) {

  26.        //ConcurrentHashMap doesn't allow null key or values

  27.        if(key == null || value == null) throw new RxCacheException("key is null or value is null");

  28.        if(cache.containsKey(key)) {

  29.            queue.remove(key);

  30.        }

  31.        if(queue.size() >= size) {

  32.            K lruKey = queue.poll();

  33.            if(lruKey != null) {

  34.                cache.remove(lruKey);

  35.            }

  36.        }

  37.        queue.add(key);

  38.        cache.put(key,value);

  39.    }


  40.    /**

  41.     * 获取最近最少使用的值

  42.     * @return

  43.     */

  44.    public V getLeastRecentlyUsed() {

  45.        K remove = queue.remove();

  46.        queue.add(remove);

  47.        return cache.get(remove);

  48.    }


  49.    public void remove(K key) {

  50.        cache.remove(key);

  51.        queue.remove(key);

  52.    }


  53.    public void clear() {

  54.        cache.clear();

  55.        queue.clear();

  56.    }

  57.    ......

  58. }

2.3 LFU

LFU是Least Frequently Used的缩写,即最近最不常用使用。

看上去跟 LRU 类似,其实它们并不相同。LRU 是淘汰最长时间未被使用的数据,而 LFU 是淘汰一定时期内被访问次数最少的数据。

LFU 会记录数据在一定时间内的使用次数。稍显复杂感兴趣的可以阅读 RxCache  中相关的源码。

三. 总结

RxCache 大体已经完成,初步可以使用。

RxCache github 地址:https://github.com/fengzhizi715/RxCache Android 版本的 RxCache github 地址:https://github.com/fengzhizi715/RxCache4a

对于 Android ,除了支持常见的持久层框架之外,还支持 RxCache 转换成 LiveData。如果想要跟 Retrofit 结合,可以通过 RxCache 的 transform 策略。

对于Java 后端,RxCache 只是一个本地缓存,不适合存放大型的数据。但是其内置的 Memory 层包含了多种缓存替换算法,不用内置的 Memory 还可以使用 Guava Cache、Caffeine 。


关注【Java与Android技术栈】

更多精彩内容请关注扫码

640?wx_fmt=jpeg


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值