Retrofit 風(fēng)格的 RxCache及其多種緩存替換算法

田園風(fēng)光.jpg

RxCache 是一個(gè)支持 Java 和 Android 的 Local Cache 猖凛。

之前的文章《給 Java 和 Android 構(gòu)建一個(gè)簡(jiǎn)單的響應(yīng)式Local Cache》舔琅、《RxCache 整合 Android 的持久層框架 greenDAO俺夕、Room》曾詳細(xì)介紹過它借卧。

目前,對(duì)框架增加一些 Annotation 以及 Cache 替換算法举哟。

一. 基于 Annotation 完成緩存操作

類似 Retrofit 風(fēng)格的方式俯渤,支持通過標(biāo)注 Annotation 來完成緩存的操作。

例如先定義一個(gè)接口侨嘀,用于定義緩存的各種操作臭挽。

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 創(chuàng)建該接口,然后可以完成各種緩存操作咬腕。

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"的數(shù)據(jù)

        if (record!=null) {

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

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

        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"的數(shù)據(jù)欢峰,返回的類型為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(),它通過動(dòng)態(tài)代理來創(chuàng)建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 實(shí)現(xiàn)了 InvocationHandler 接口,是創(chuàng)建代理類的調(diào)用處理器煞赢。

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吹截。

其中瘦陈,CacheMethod 是最核心的 Annotation,它取決于 rxCache 使用哪個(gè)方法波俄。CacheMethod 支持的方法類型包括:獲取晨逝、保存、刪除緩存懦铺。當(dāng) CacheMethod 的 methodType 是 GET 類型捉貌,則可能會(huì)返回 RxJava 的各種 Observable 類型,或者還是返回所存儲(chǔ)的對(duì)象類型冬念。

CacheKey 是任何方法都需要使用的 Annotation趁窃。CacheLifecycle 只有保存緩存時(shí)才會(huì)使用。

二. 支持多種緩存替換算法

RxCache 包含了兩級(jí)緩存: Memory 和 Persistence 急前。

Memory 的默認(rèn)實(shí)現(xiàn) FIFOMemoryImpl醒陆、LRUMemoryImpl、LFUMemoryImpl 分別使用 FIFO裆针、LRU刨摩、LFU 算法來緩存數(shù)據(jù)。

2.1 FIFO

通過使用 LinkedList 存放緩存的 keys世吨,ConcurrentHashMap 存放緩存的數(shù)據(jù)澡刹,就可以實(shí)現(xiàn) FIFO。

2.2 LRU

LRU是Least Recently Used的縮寫耘婚,即最近最少使用罢浇,常用于頁面置換算法,是為虛擬頁式存儲(chǔ)管理服務(wù)的沐祷。

使用 ConcurrentHashMap 和 ConcurrentLinkedQueue 實(shí)現(xiàn)該算法己莺。如果某個(gè)數(shù)據(jù)已經(jīng)存放在緩存中,則從 queue 中刪除并添加到 queue 的第一個(gè)位置戈轿。如果緩存已滿,則從 queue 中刪除最后面的數(shù)據(jù)阵子。并把新的數(shù)據(jù)添加到緩存思杯。

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 類似色乾,其實(shí)它們并不相同。LRU 是淘汰最長(zhǎng)時(shí)間未被使用的數(shù)據(jù)领突,而 LFU 是淘汰一定時(shí)期內(nèi)被訪問次數(shù)最少的數(shù)據(jù)暖璧。

LFU 會(huì)記錄數(shù)據(jù)在一定時(shí)間內(nèi)的使用次數(shù)。稍顯復(fù)雜感興趣的可以閱讀 RxCache 中相關(guān)的源碼君旦。

三. 總結(jié)

RxCache 大體已經(jīng)完成澎办,初步可以使用嘲碱。

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

對(duì)于 Android ,除了支持常見的持久層框架之外局蚀,還支持 RxCache 轉(zhuǎn)換成 LiveData麦锯。如果想要跟 Retrofit 結(jié)合,可以通過 RxCache 的 transform 策略琅绅。

對(duì)于Java 后端扶欣,RxCache 只是一個(gè)本地緩存,不適合存放大型的數(shù)據(jù)千扶。但是其內(nèi)置的 Memory 層包含了多種緩存替換算法料祠,不用內(nèi)置的 Memory 還可以使用 Guava Cache、Caffeine 澎羞。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末髓绽,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子煤痕,更是在濱河造成了極大的恐慌梧宫,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,509評(píng)論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件摆碉,死亡現(xiàn)場(chǎng)離奇詭異塘匣,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)巷帝,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,806評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門忌卤,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人楞泼,你說我怎么就攤上這事驰徊。” “怎么了堕阔?”我有些...
    開封第一講書人閱讀 163,875評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵棍厂,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我超陆,道長(zhǎng)牺弹,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,441評(píng)論 1 293
  • 正文 為了忘掉前任时呀,我火速辦了婚禮张漂,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘谨娜。我一直安慰自己航攒,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,488評(píng)論 6 392
  • 文/花漫 我一把揭開白布趴梢。 她就那樣靜靜地躺著漠畜,像睡著了一般币他。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上盆驹,一...
    開封第一講書人閱讀 51,365評(píng)論 1 302
  • 那天圆丹,我揣著相機(jī)與錄音,去河邊找鬼躯喇。 笑死辫封,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的廉丽。 我是一名探鬼主播倦微,決...
    沈念sama閱讀 40,190評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼正压!你這毒婦竟也來了欣福?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,062評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤焦履,失蹤者是張志新(化名)和其女友劉穎拓劝,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體嘉裤,經(jīng)...
    沈念sama閱讀 45,500評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡郑临,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,706評(píng)論 3 335
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了屑宠。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片厢洞。...
    茶點(diǎn)故事閱讀 39,834評(píng)論 1 347
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖典奉,靈堂內(nèi)的尸體忽然破棺而出躺翻,到底是詐尸還是另有隱情,我是刑警寧澤卫玖,帶...
    沈念sama閱讀 35,559評(píng)論 5 345
  • 正文 年R本政府宣布公你,位于F島的核電站,受9級(jí)特大地震影響假瞬,放射性物質(zhì)發(fā)生泄漏省店。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,167評(píng)論 3 328
  • 文/蒙蒙 一笨触、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧雹舀,春花似錦芦劣、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,779評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽寸认。三九已至,卻和暖如春串慰,著一層夾襖步出監(jiān)牢的瞬間偏塞,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,912評(píng)論 1 269
  • 我被黑心中介騙來泰國打工邦鲫, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留灸叼,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,958評(píng)論 2 370
  • 正文 我出身青樓庆捺,卻偏偏與公主長(zhǎng)得像古今,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子滔以,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,779評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容

  • 1我們都聽過 cache你画,當(dāng)你問他們是什么是緩存的時(shí)候抵碟,他們會(huì)給你一個(gè)完美的答案,可是他們不知道緩存是怎么構(gòu)建的坏匪,...
    織田信長(zhǎng)閱讀 1,507評(píng)論 1 20
  • 緩存是最直接有效提升系統(tǒng)性能的手段之一拟逮。個(gè)人認(rèn)為用好用對(duì)緩存是優(yōu)秀程序員的必備基本素質(zhì)。 本文結(jié)合實(shí)際開發(fā)經(jīng)驗(yàn)剥槐,從...
    Java小生閱讀 809評(píng)論 1 3
  • 理論總結(jié) 它要解決什么樣的問題唱歧? 數(shù)據(jù)的訪問、存取粒竖、計(jì)算太慢颅崩、太不穩(wěn)定、太消耗資源蕊苗,同時(shí)沿后,這樣的操作存在重復(fù)性。因...
    jiangmo閱讀 2,851評(píng)論 0 11
  • 前言 主題是Mybatis一級(jí)和二級(jí)緩存的應(yīng)用及源碼分析朽砰。希望在本場(chǎng)chat結(jié)束后尖滚,能夠幫助讀者朋友明白以下三點(diǎn)。...
    余平的余_余平的平閱讀 1,327評(píng)論 0 12
  • 當(dāng)夏日的傍晚溫暖的風(fēng)徐徐吹來瞧柔,夾雜著泥土與花的芬芳漆弄,我在回想一天的時(shí)光。當(dāng)我抬頭看法國梧桐時(shí)造锅,輕輕的瞥一眼天空撼唾,灰...
    簟紋燈影閱讀 295評(píng)論 0 0