Retrofit 風格的 RxCache及其多種緩存替換算法

田園風光.jpg

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

以前的文章《給 Java 和 Android 構建一個簡單的響應式Local Cache》《RxCache 整合 Android 的持久層框架 greenDAO、Room》曾詳細介紹過它。git

目前,對框架增長一些 Annotation 以及 Cache 替換算法。github

一. 基於 Annotation 完成緩存操做

相似 Retrofit 風格的方式,支持經過標註 Annotation 來完成緩存的操做。算法

例如先定義一個接口,用於定義緩存的各類操做。後端

public interface Provider {

    @CacheKey("user")
    @CacheMethod(methodType = MethodType.GET)
    <T> Record<T> getData(@CacheClass Class<T> clazz);


    @CacheKey("user")
    @CacheMethod(methodType = MethodType.SAVE)
    @CacheLifecycle(duration = 2000)
    void putData(@CacheValue User user);


    @CacheKey("user")
    @CacheMethod(methodType = MethodType.REMOVE)
    void removeUser();

    @CacheKey("test")
    @CacheMethod(methodType = MethodType.GET, observableType = ObservableType.MAYBE)
    <T> Maybe<Record<T>> getMaybe(@CacheClass Class<T> clazz);
}
複製代碼

經過 CacheProvider 建立該接口,而後能夠完成各類緩存操做。緩存

public class TestCacheProvider {

    public static void main(String[] args) {


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

        RxCache rxCache = RxCache.getRxCache();

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

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

        User u = new User();
        u.name = "tony";
        u.password = "123456";

        provider.putData(u); // 將u存入緩存中

        Record<User> record = provider.getData(User.class); // 從緩存中獲取key="user"的數據

        if (record!=null) {

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

        provider.removeUser(); // 從緩存中刪除key="user"的數據

        record = provider.getData(User.class);

        if (record==null) {

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

        User u2 = new User();
        u2.name = "tony2";
        u2.password = "000000";
        rxCache.save("test",u2);

        Maybe<Record<User>> maybe = provider.getMaybe(User.class); // 從緩存中獲取key="test"的數據,返回的類型爲Maybe
        maybe.subscribe(new Consumer<Record<User>>() {
            @Override
            public void accept(Record<User> userRecord) throws Exception {

                User user = userRecord.getData();
                if (user!=null) {

                    System.out.println(user.name);
                    System.out.println(user.password);
                }
            }
        });
    }
}
複製代碼

CacheProvider 核心是 create(),它經過動態代理來建立Provider。框架

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

        CacheProxy cacheProxy = new CacheProxy(rxCache);

        try {
            return (T) Proxy.newProxyInstance(CacheProvider.class.getClassLoader(), new Class[]{clazz}, cacheProxy);
        } catch (Exception e) {
            e.printStackTrace();
        }

        return null;
    }
複製代碼

其中,CacheProxy 實現了 InvocationHandler 接口,是建立代理類的調用處理器。ide

package com.safframework.rxcache.proxy;

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

import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

/** * Created by tony on 2018/10/30. */
public class CacheProxy implements InvocationHandler {

    RxCache rxCache;

    public CacheProxy(RxCache rxCache) {

        this.rxCache = rxCache;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

        CacheMethod cacheMethod = method.getAnnotation(CacheMethod.class);
        CacheKey cacheKey = method.getAnnotation(CacheKey.class);
        CacheLifecycle cacheLifecycle = method.getAnnotation(CacheLifecycle.class);

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

        Class cacheClazz = null;
        Object cacheValue = null;

        if (allParamsAnnotations != null) {
            for (int i = 0; i < allParamsAnnotations.length; i++) {
                Annotation[] paramAnnotations = allParamsAnnotations[i];
                if (paramAnnotations != null) {
                    for (Annotation annotation : paramAnnotations) {
                        if (annotation instanceof CacheClass) {
                            cacheClazz = (Class) args[i];
                        }

                        if (annotation instanceof CacheValue) {
                            cacheValue = args[i];
                        }
                    }
                }
            }
        }

        if (cacheMethod!=null) {

            MethodType methodType = cacheMethod.methodType();

            long duration = -1;

            if (cacheLifecycle != null) {
                duration = cacheLifecycle.duration();
            }

            if (methodType == MethodType.GET) {

                ObservableType observableType = cacheMethod.observableType();

                if (observableType==ObservableType.NOUSE) {

                    return  rxCache.get(cacheKey.value(),cacheClazz);
                } else if (observableType == ObservableType.OBSERVABLE){

                    return  rxCache.load2Observable(cacheKey.value(),cacheClazz);
                } else if (observableType==ObservableType.FLOWABLE) {

                    return  rxCache.load2Flowable(cacheKey.value(),cacheClazz);
                } else if (observableType==ObservableType.SINGLE) {

                    return  rxCache.load2Single(cacheKey.value(),cacheClazz);
                } else if (observableType==ObservableType.MAYBE) {

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

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

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

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

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

        return null;
    }
}
複製代碼

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

其中,CacheMethod 是最核心的 Annotation,它取決於 rxCache 使用哪一個方法。CacheMethod 支持的方法類型包括:獲取、保存、刪除緩存。當 CacheMethod 的 methodType 是 GET 類型,則可能會返回 RxJava 的各類 Observable 類型,或者仍是返回所存儲的對象類型。ui

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 中刪除最後面的數據。並把新的數據添加到緩存。

public class LRUCache<K,V> {

    private Map<K,V> cache = null;
    private AbstractQueue<K> queue = null;
    private int size = 0;

    public LRUCache() {

        this(Constant.DEFAULT_CACHE_SIZE);
    }

    public LRUCache(int size) {

        this.size = size;
        cache = new ConcurrentHashMap<K,V>(size);
        queue = new ConcurrentLinkedQueue<K>();
    }

    public boolean containsKey(K key) {

        return cache.containsKey(key);
    }

    public V get(K key) {

        //Recently accessed, hence move it to the tail
        queue.remove(key);
        queue.add(key);
        return cache.get(key);
    }

    public V getSilent(K key) {

        return cache.get(key);
    }

    public void put(K key, V value) {

        //ConcurrentHashMap doesn't allow null key or values
        if(key == null || value == null) throw new RxCacheException("key is null or value is null");

        if(cache.containsKey(key)) {
            queue.remove(key);
        }

        if(queue.size() >= size) {
            K lruKey = queue.poll();
            if(lruKey != null) {
                cache.remove(lruKey);
            }
        }

        queue.add(key);
        cache.put(key,value);
    }

    /** * 獲取最近最少使用的值 * @return */
    public V getLeastRecentlyUsed() {

        K remove = queue.remove();
        queue.add(remove);
        return cache.get(remove);
    }

    public void remove(K key) {

        cache.remove(key);
        queue.remove(key);
    }

    public void clear() {

        cache.clear();
        queue.clear();
    }
    ......
}
複製代碼

2.3 LFU

LFU是Least Frequently Used的縮寫,即最近最不經常使用使用。

看上去跟 LRU 相似,其實它們並不相同。LRU 是淘汰最長時間未被使用的數據,而 LFU 是淘汰必定時期內被訪問次數最少的數據。

LFU 會記錄數據在必定時間內的使用次數。稍顯複雜感興趣的能夠閱讀 RxCache  中相關的源碼。

三. 總結

RxCache 大致已經完成,初步可使用。

RxCache github 地址:github.com/fengzhizi71… Android 版本的 RxCache github 地址:github.com/fengzhizi71…

對於 Android ,除了支持常見的持久層框架以外,還支持 RxCache 轉換成 LiveData。若是想要跟 Retrofit 結合,能夠經過 RxCache 的 transform 策略。

對於Java 後端,RxCache 只是一個本地緩存,不適合存放大型的數據。可是其內置的 Memory 層包含了多種緩存替換算法,不用內置的 Memory 還可使用 Guava Cache、Caffeine 。

RxCache 系列的相關文章:

  1. ReentrantReadWriteLock讀寫鎖及其在 RxCache 中的使用
  2. 堆外內存及其在 RxCache 中的使用
  3. RxCache 整合 Android 的持久層框架 greenDAO、Room
  4. 給 Java 和 Android 構建一個簡單的響應式Local Cache

Java與Android技術棧:每週更新推送原創技術文章,歡迎掃描下方的公衆號二維碼並關注,期待與您的共同成長和進步。

相關文章
相關標籤/搜索