Guava Cache

原文

使用Guava cache構(gòu)建本地緩存 - sameLuo的個人空間 - OSCHINA

Guava Cache -- Java 應用緩存神器 - 云+社區(qū) - 騰訊云

Google Guava緩存

最近需要用到緩存來存放臨時數(shù)據(jù),又不想采用Redis芦鳍,Java自帶的Map功能太少档插,發(fā)現(xiàn)Google的Guava提供的Cache模塊功能很強大算灸,于是選擇使用它。

本地緩存

本地緩存作用就是提高系統(tǒng)的運行速度巧婶,是一種空間換時間的取舍缸血。它實質(zhì)上是一個做key-value查詢的字典物蝙,但是相對于我們常用HashMap它又有以下特點:

1.并發(fā)性:由于目前的應用大都是多線程的澜汤,所以緩存需要支持并發(fā)的寫入蚜迅。

2.過期策略:在某些場景中,我們可能會希望緩存的數(shù)據(jù)有一定“保質(zhì)期”俊抵,過期策略可以固定時間谁不,例如緩存寫入10分鐘后過期。也可以是相對時間徽诲,例如10分鐘內(nèi)未訪問則使緩存過期(類似于servlet中的session)拍谐。在java中甚至可以使用軟引用,弱引用的過期策略馏段。

3.淘汰策略:由于本地緩存是存放在內(nèi)存中,我們往往需要設置一個容量上限和淘汰策略來防止出現(xiàn)內(nèi)存溢出的情況践瓷。

緩存應當具備的屬性為:

1院喜、能夠配置緩存的大小,保持可控的Memory晕翠。

2处硬、適應多種場景的數(shù)據(jù)expire策略齐婴。

3、在高并發(fā)情況下、能夠正常緩存的更新以及返回默穴。

Guava Cache適用于:

你愿意消耗一些內(nèi)存空間來提升速度。
你預料到某些鍵會被查詢一次以上趴生。
緩存中存放的數(shù)據(jù)總量不會超出內(nèi)存容量

緩存的最大容量與淘汰策略

由于本地緩存是將計算結(jié)果緩存到內(nèi)存中短蜕,所以我們往往需要設置一個最大容量來防止出現(xiàn)內(nèi)存溢出的情況。這個容量可以是緩存對象的數(shù)量碌尔,也可以是一個具體的內(nèi)存大小浇辜。在Guva中僅支持設置緩存對象的數(shù)量。

當緩存數(shù)量逼近或大于我們所設置的最大容量時唾戚,為了將緩存數(shù)量控制在我們所設定的閾值內(nèi)柳洋,就需要丟棄掉一些數(shù)據(jù)。由于緩存的最大容量恒定叹坦,為了提高緩存的命中率熊镣,我們需要盡量丟棄那些我們之后不再經(jīng)常訪問的數(shù)據(jù),保留那些即將被訪問的數(shù)據(jù)。為了達到以上目的绪囱,我們往往會制定一些緩存淘汰策略测蹲,常用的緩存淘汰策略有以下幾種:

1.FIFO:First In First Out,先進先出毕箍。

一般采用隊列的方式實現(xiàn)弛房。這種淘汰策略僅僅是保證了緩存數(shù)量不超過我們所設置的閾值,而完全沒有考慮緩存的命中率而柑。所以在這種策略極少被使用文捶。

2.LRU:Least Recently Used,最近最少使用媒咳;

該算法其核心思想是“如果數(shù)據(jù)最近被訪問過粹排,那么將來被訪問的幾率也更高”。

所以該算法是淘汰最后一次使用時間離當前最久的緩存數(shù)據(jù)涩澡,保留最近訪問的數(shù)據(jù)顽耳。所以該種算法非常適合緩存“熱點數(shù)據(jù)”。

但是該算法在緩存周期性數(shù)據(jù)時妙同,就會出現(xiàn)緩存污染射富,也就是淘汰了即將訪問的數(shù)據(jù),反而把不常用的數(shù)據(jù)讀取到緩存中粥帚。

為了解決這個問題胰耗,后續(xù)也出現(xiàn)了如LRU-K,Two queues芒涡,Multi Queue等進階算法柴灯。

3.LFU:Least Frequently Used,最不經(jīng)常使用费尽。

該算法的核心思想是“如果數(shù)據(jù)在以前被訪問的次數(shù)最多赠群,那么將來被訪問的幾率就會更高”。所以該算法淘汰的是歷史訪問次數(shù)最少的數(shù)據(jù)旱幼。

一般情況下查描,LFU效率要優(yōu)于LRU,且能夠避免周期性或者偶發(fā)性的操作導致緩存命中率下降的問題柏卤。但LFU需要記錄數(shù)據(jù)的歷史訪問記錄叹誉,一旦數(shù)據(jù)訪問模式改變,LFU需要更長時間來適用新的訪問模式闷旧,即:LFU存在歷史數(shù)據(jù)影響將來數(shù)據(jù)的“緩存污染”效用长豁。

后續(xù)出現(xiàn)LFU*,LFU-Aging忙灼,Window-LFU等改進算法匠襟。

合理的使用淘汰算法能夠很明顯的提升緩存命中率钝侠,但是也不應該一味的追求命中率,而是應在命中率和資源消耗中找到一個平衡酸舍。

在guava中默認使用LRU淘汰算法帅韧,而且在不修改源碼的情況下也不支持自定義淘汰算法。

使用Guava構(gòu)建緩存

// 通過CacheBuilder構(gòu)建一個緩存實例
Cache<String, String> cache = CacheBuilder.newBuilder()
                .maximumSize(100) // 設置緩存的最大容量
                .expireAfterWrite(1, TimeUnit.MINUTES) // 設置緩存在寫入一分鐘后失效
                .concurrencyLevel(10) // 設置并發(fā)級別為10
                .recordStats() // 開啟緩存統(tǒng)計
                .build();
// 放入緩存
cache.put("key", "value");
// 獲取緩存
String value = cache.getIfPresent("key");

Guava的緩存有許多配置選項啃勉,所以為了簡化緩存的創(chuàng)建過程忽舟,使用了Builder設計模式

上面的代碼演示了使用Guava創(chuàng)建了一個基于內(nèi)存的本地緩存,并指定了一些緩存的參數(shù)淮阐,如緩存容量叮阅、緩存過期時間、并發(fā)級別等泣特,隨后通過put方法放入一個緩存并使用getIfPresent來獲取它浩姥。

Cache與LoadingCache

使用CacheBuilder我們能構(gòu)建出兩種類型的cache,他們分別是Cache與LoadingCache状您。

Cache

Cache是通過CacheBuilder的build()方法構(gòu)建勒叠,它是Gauva提供的最基本的緩存接口,并且它提供了一些常用的緩存api:


// 放入/覆蓋一個緩存
cache.put("k1", "v1");
// 獲取一個緩存膏孟,如果該緩存不存在則返回一個null值
Object value = cache.getIfPresent("k1");
// 獲取緩存眯分,當緩存不存在時,則通Callable進行加載并返回柒桑。該操作是原子
Object getValue = cache.get("k1", new Callable<Object>() {
    @Override
    public Object call() throws Exception {
        return null;
    }
});

java8也可以采用lambda表達式來代替匿名內(nèi)部類

Object getValue = cache.get("k1", () -> {
    return null;
});

LoadingCache

LoadingCache繼承自Cache弊决,在構(gòu)建LoadingCache時,需要通過CacheBuilder的build(CacheLoader<? super K1, V1> loader)方法構(gòu)建:

CacheBuilder.newBuilder()
        .build(new CacheLoader<String, String>() {
            @Override
            public String load(String key) throws Exception {
                // 緩存加載邏輯
                ...
            }
        });

LoadingCache幕垦,顧名思義,它能夠通過CacheLoader自發(fā)的加載緩存:

 LoadingCache<Object, Object> loadingCache = CacheBuilder.newBuilder().build(new CacheLoader<Object, Object>() {
            @Override
            public Object load(Object key) throws Exception {
                return null;
            }
        });
// 獲取緩存傅联,當緩存不存在時先改,會通過CacheLoader自動加載,該方法會拋出ExecutionException異常
loadingCache.get("k1");
// 以不安全的方式獲取緩存蒸走,當緩存不存在時仇奶,會通過CacheLoader自動加載,該方法不會拋出異常
loadingCache.getUnchecked("k1");

緩存的并發(fā)級別

Guava提供了設置并發(fā)級別的api比驻,使得緩存支持并發(fā)的寫入和讀取该溯。同ConcurrentHashMap類似Guava cache的并發(fā)也是通過分離鎖實現(xiàn)。在一般情況下别惦,將并發(fā)級別設置為服務器cpu核心數(shù)是一個比較不錯的選擇狈茉。

CacheBuilder.newBuilder()
        // 設置并發(fā)級別為cpu核心數(shù)
        .concurrencyLevel(Runtime.getRuntime().availableProcessors()) 
        .build();

緩存的初始容量

我們在構(gòu)建緩存時可以為緩存設置一個合理大小初始容量,由于Guava的緩存使用了分離鎖的機制掸掸,擴容的代價非常昂貴氯庆。所以合理的初始容量能夠減少緩存容器的擴容次數(shù)蹭秋。

CacheBuilder.newBuilder()
        // 設置初始容量為100
        .initialCapacity(100)
        .build();

緩存的回收

在前文提到過,在構(gòu)建本地緩存時堤撵,我們應該指定一個最大容量來防止出現(xiàn)內(nèi)存溢出的情況仁讨。在guava中除了提供基于數(shù)量,和基于內(nèi)存容量兩種回收策略外实昨,還提供了基于引用的回收洞豁。

基于數(shù)量/容量的回收

基于最大數(shù)量的回收策略非常簡單,我們只需指定緩存的最大數(shù)量maximumSize即可荒给,maximumSize 設定了該緩存的最大存儲單位(key)個數(shù):

CacheBuilder.newBuilder()
        .maximumSize(100) // 緩存數(shù)量上限為100
        .build();

使用基于最大容量的的回收策略時丈挟,我們需要設置2個必要參數(shù):

maximumWeigh;用于指定最大容量锐墙,maximumWeight 是根據(jù)設定緩存數(shù)據(jù)的最大值礁哄。

Weigher;在加載緩存時用于計算緩存容量大小溪北。

這里我們例舉一個key和value都是String類型緩存:

CacheBuilder.newBuilder()
        .maximumWeight(1024 * 1024 * 1024) // 設置最大容量為 1M
        // 設置用來計算緩存容量的Weigher
        .weigher(new Weigher<String, String>() { 
            @Override
            public int weigh(String key, String value) {
                return key.getBytes().length + value.getBytes().length;
            }
        }).build();

當緩存的最大數(shù)量/容量逼近或超過我們所設置的最大值時桐绒,Guava就會使用LRU算法對之前的緩存進行回收。

基于軟/弱引用的回收

基于引用的回收策略之拨,是java中獨有的茉继。在java中有對象自動回收機制,依據(jù)程序員創(chuàng)建對象的方式不同蚀乔,將對象由強到弱分為強引用烁竭、軟引用、弱引用吉挣、虛引用派撕。對于這幾種引用他們有以下區(qū)別:

強引用

強引用是使用最普遍的引用。如果一個對象具有強引用睬魂,那垃圾回收器絕不會回收它终吼。

Object o=new Object(); 

當內(nèi)存空間不足,垃圾回收器不會自動回收一個被引用的強引用對象氯哮,而是會直接拋出OutOfMemoryError錯誤际跪,使程序異常終止。

軟引用

相對于強引用喉钢,軟引用是一種不穩(wěn)定的引用方式姆打,如果一個對象具有軟引用,當內(nèi)存充足時肠虽,GC不會主動回收軟引用對象幔戏,而當內(nèi)存不足時軟引用對象就會被回收。

SoftReference<Object> softRef=new SoftReference<Object>(new Object()); // 軟引用
Object object = softRef.get(); // 獲取軟引用

使用軟引用能防止內(nèi)存泄露税课,增強程序的健壯性评抚。但是一定要做好null檢測豹缀。

弱引用

弱引用是一種比軟引用更不穩(wěn)定的引用方式,因為無論內(nèi)存是否充足慨代,弱引用對象都有可能被回收邢笙。

WeakReference<Object> weakRef = new WeakReference<Object>(new Object()); // 弱引用
Object obj = weakRef.get(); // 獲取弱引用

虛引用

而虛引用這種引用方式就是形同虛設,因為如果一個對象僅持有虛引用侍匙,那么它就和沒有任何引用一樣氮惯。在實踐中也幾乎沒有使用。

在Guava cache中支持想暗,軟/弱引用的緩存回收方式妇汗。使用這種方式能夠極大的提高內(nèi)存的利用率,并且不會出現(xiàn)內(nèi)存溢出的異常说莫。

CacheBuilder.newBuilder()
        .weakKeys() // 使用弱引用存儲鍵杨箭。當鍵沒有其它(強或軟)引用時,該緩存可能會被回收储狭。
        .weakValues() // 使用弱引用存儲值互婿。當值沒有其它(強或軟)引用時,該緩存可能會被回收辽狈。
        .softValues() // 使用軟引用存儲值慈参。當內(nèi)存不足并且該值其它強引用引用時,該緩存就會被回收
        .build();

通過軟/弱引用的回收方式刮萌,相當于將緩存回收任務交給了GC驮配,使得緩存的命中率變得十分的不穩(wěn)定,在非必要的情況下着茸,還是推薦基于數(shù)量和容量的回收壮锻。

顯式回收

在緩存構(gòu)建完畢后,我們可以通過Cache提供的接口涮阔,顯式的對緩存進行回收猜绣,例如:

// 構(gòu)建一個緩存
Cache<String, String> cache = CacheBuilder.newBuilder().build();
// 回收key為k1的緩存
cache.invalidate("k1");
// 批量回收key為k1、k2的緩存
List<String> needInvalidateKeys = new ArrayList<>();
needInvalidateKeys.add("k1");
needInvalidateKeys.add("k2");
cache.invalidateAll(needInvalidateKeys);
// 回收所有緩存
cache.invalidateAll();

移除監(jiān)聽器

通過CacheBuilder.removalListener(RemovalListener)澎语,你可以聲明一個監(jiān)聽器途事,以便緩存項被移除時做一些額外操作验懊。緩存項被移除時擅羞,RemovalListener<會獲取移除通知[RemovalNotification],其中包含移除原因[RemovalCause]义图、鍵和值减俏。

請注意,RemovalListener拋出的任何異常都會在記錄到日志后被丟棄[swallowed]碱工。

CacheLoader<Key, DatabaseConnection> loader = new CacheLoader<Key, DatabaseConnection> () {
    public DatabaseConnection load(Key key) throws Exception {
        return openConnection(key);
    }
};

RemovalListener<Key, DatabaseConnection> removalListener = new RemovalListener<Key, DatabaseConnection>() {
    public void onRemoval(RemovalNotification<Key, DatabaseConnection> removal) {
        DatabaseConnection conn = removal.getValue();
        conn.close(); // tear down properly
    }
};

return CacheBuilder.newBuilder()
    .expireAfterWrite(2, TimeUnit.MINUTES)
    .removalListener(removalListener)
    .build(loader);

警告:默認情況下娃承,監(jiān)聽器方法是在移除緩存時同步調(diào)用的奏夫。因為緩存的維護和請求響應通常是同時進行的,代價高昂的監(jiān)聽器方法在同步模式下會拖慢正常的緩存請求历筝。在這種情況下酗昼,你可以使用RemovalListeners.asynchronous(RemovalListener, Executor)把監(jiān)聽器裝飾為異步操作。

緩存的過期策略與刷新

Guava也提供了緩存的過期策略和刷新策略梳猪。

緩存過期策略

緩存的過期策略分為固定時間和相對時間麻削。

固定時間一般是指寫入后多長時間過期,例如我們構(gòu)建一個寫入10分鐘后過期的緩存:

CacheBuilder.newBuilder()
        .expireAfterWrite(10, TimeUnit.MINUTES) // 寫入10分鐘后過期
        .build();

// java8后可以使用Duration設置
CacheBuilder.newBuilder()
        .expireAfterWrite(Duration.ofMinutes(10))
        .build();

相對時間一般是相對于訪問時間春弥,也就是每次訪問后呛哟,會重新刷新該緩存的過期時間,這有點類似于servlet中的session過期時間匿沛,例如構(gòu)建一個在10分鐘內(nèi)未訪問則過期的緩存:

CacheBuilder.newBuilder()
        .expireAfterAccess(10, TimeUnit.MINUTES) //在10分鐘內(nèi)未訪問則過期
        .build();

// java8后可以使用Duration設置
CacheBuilder.newBuilder()
        .expireAfterAccess(Duration.ofMinutes(10))
        .build();

緩存刷新

在Guava cache中支持定時刷新和顯式刷新兩種方式扫责,其中只有LoadingCache能夠進行定時刷新。

定時刷新

在進行緩存定時刷新時逃呼,我們需要指定緩存的刷新間隔鳖孤,和一個用來加載緩存的CacheLoader,當達到刷新時間間隔后蜘渣,下一次獲取緩存時淌铐,會調(diào)用CacheLoader的load方法刷新緩存。例如構(gòu)建個刷新頻率為10分鐘的緩存:

CacheBuilder.newBuilder()
        // 設置緩存在寫入10分鐘后蔫缸,通過CacheLoader的load方法進行刷新
        .refreshAfterWrite(10, TimeUnit.SECONDS)
        // jdk8以后可以使用 Duration
        // .refreshAfterWrite(Duration.ofMinutes(10))
        .build(new CacheLoader<String, String>() {
            @Override
            public String load(String key) throws Exception {
                // 緩存加載邏輯
                ...
            }
        });

顯式刷新

在緩存構(gòu)建完畢后腿准,我們可以通過Cache提供的一些借口方法,顯式的對緩存進行刷新覆蓋拾碌,例如:

// 構(gòu)建一個緩存
Cache<String, String> cache = CacheBuilder.newBuilder().build();
// 使用put進行覆蓋刷新
cache.put("k1", "v1");
// 使用Map的put方法進行覆蓋刷新
cache.asMap().put("k1", "v1");
// 使用Map的putAll方法進行批量覆蓋刷新
Map<String,String> needRefreshs = new HashMap<>();
needRefreshs.put("k1", "v1");
cache.asMap().putAll(needRefreshs);
// 使用ConcurrentMap的replace方法進行覆蓋刷新
cache.asMap().replace("k1", "v1");

對于LoadingCache吐葱,由于它能夠自動的加載緩存,所以在進行刷新時校翔,不需要顯式的傳入緩存的值:

LoadingCache<String, String> loadingCache = CacheBuilder
            .newBuilder()
            .build(new CacheLoader<String, String>() {
                @Override
                public String load(String key) throws Exception {
                    // 緩存加載邏輯
                    return null;
                }
            });
// loadingCache 在進行刷新時無需顯式的傳入 value
loadingCache.refresh("k1");

統(tǒng)計

CacheBuilder.recordStats()用來開啟Guava Cache的統(tǒng)計功能弟跑。統(tǒng)計打開后,Cache.stats()方法會返回CacheStats對象以提供如下統(tǒng)計信息:

此外,還有其他很多統(tǒng)計信息奈嘿。這些統(tǒng)計信息對于調(diào)整緩存設置是至關(guān)重要的貌虾,在性能要求高的應用中我們建議密切關(guān)注這些數(shù)據(jù)。
Guava 提供了recordStats()方法裙犹,相當于啟動了記錄模式尽狠,通過Cache.stats()方法可以獲取CacheStats對象衔憨,里面存儲著緩存的使用情況,通過觀察它就可以知道緩存的命中率袄膏,加載耗時等信息践图,有了這些數(shù)據(jù)的反饋就可以調(diào)整的緩存的大小以及其他的優(yōu)化工作了。

asMap視圖

asMap視圖提供了緩存的ConcurrentMap形式沉馆,但asMap視圖與緩存的交互需要注意:

  • cache.asMap()包含當前所有加載到緩存的項平项。因此相應地,cache.asMap().keySet()包含當前所有已加載鍵;
  • asMap().get(key)實質(zhì)上等同于cache.getIfPresent(key)悍及,而且不會引起緩存項的加載闽瓢。這和Map的語義約定一致。
  • 所有讀寫操作都會重置相關(guān)緩存項的訪問時間心赶,包括Cache.asMap().get(Object)方法和Cache.asMap().put(K, V)方法扣讼,但不包括Cache.asMap().containsKey(Object)方法,也不包括在Cache.asMap()的集合視圖上的操作缨叫。比如椭符,遍歷Cache.asMap().entrySet()不會重置緩存項的讀取時間。

常見問題

緩存使用的最常見的問題耻姥,上文中销钝,提到緩存數(shù)據(jù)拉取出來后,需要添加一些關(guān)于每一個訪問用戶的額外信息琐簇,例如拉取出上課列表后蒸健,每一個用戶針對課程的狀態(tài)是不一樣的(報名狀態(tài)),通常會犯的一個錯誤就是直接在緩存數(shù)據(jù)基礎(chǔ)上進行修改婉商,通常我們緩存的對象會是一個Map似忧,或者List,對其引用的修改其實已經(jīng)修改了對應值本身丈秩,這樣會造成數(shù)據(jù)的混亂盯捌。因此記得在修改之前將緩存數(shù)據(jù)先深拷貝。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末蘑秽,一起剝皮案震驚了整個濱河市饺著,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌肠牲,老刑警劉巖幼衰,帶你破解...
    沈念sama閱讀 218,036評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異埂材,居然都是意外死亡塑顺,警方通過查閱死者的電腦和手機汤求,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,046評論 3 395
  • 文/潘曉璐 我一進店門俏险,熙熙樓的掌柜王于貴愁眉苦臉地迎上來严拒,“玉大人,你說我怎么就攤上這事竖独】氵耄” “怎么了?”我有些...
    開封第一講書人閱讀 164,411評論 0 354
  • 文/不壞的土叔 我叫張陵莹痢,是天一觀的道長种蘸。 經(jīng)常有香客問我,道長竞膳,這世上最難降的妖魔是什么航瞭? 我笑而不...
    開封第一講書人閱讀 58,622評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮坦辟,結(jié)果婚禮上刊侯,老公的妹妹穿的比我還像新娘。我一直安慰自己锉走,他們只是感情好滨彻,可當我...
    茶點故事閱讀 67,661評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著挪蹭,像睡著了一般亭饵。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上梁厉,一...
    開封第一講書人閱讀 51,521評論 1 304
  • 那天辜羊,我揣著相機與錄音,去河邊找鬼词顾。 笑死只冻,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的计技。 我是一名探鬼主播喜德,決...
    沈念sama閱讀 40,288評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼垮媒!你這毒婦竟也來了舍悯?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,200評論 0 276
  • 序言:老撾萬榮一對情侶失蹤睡雇,失蹤者是張志新(化名)和其女友劉穎萌衬,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體它抱,經(jīng)...
    沈念sama閱讀 45,644評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡秕豫,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,837評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片混移。...
    茶點故事閱讀 39,953評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡祠墅,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出歌径,到底是詐尸還是另有隱情毁嗦,我是刑警寧澤,帶...
    沈念sama閱讀 35,673評論 5 346
  • 正文 年R本政府宣布回铛,位于F島的核電站狗准,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏茵肃。R本人自食惡果不足惜腔长,卻給世界環(huán)境...
    茶點故事閱讀 41,281評論 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望验残。 院中可真熱鬧饼酿,春花似錦、人聲如沸胚膊。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,889評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽紊婉。三九已至药版,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間喻犁,已是汗流浹背槽片。 一陣腳步聲響...
    開封第一講書人閱讀 33,011評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留肢础,地道東北人还栓。 一個月前我還...
    沈念sama閱讀 48,119評論 3 370
  • 正文 我出身青樓,卻偏偏與公主長得像传轰,于是被迫代替她去往敵國和親剩盒。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,901評論 2 355

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

  • 使用場景 緩存在很多場景下都是相當有用的慨蛙。例如辽聊,計算或檢索一個值的代價很高,并且對同樣的輸入需要不止一次獲取值的時...
    jiangmo閱讀 797評論 0 3
  • 原文鏈接:原文鏈接 注:這篇文章是我自己根據(jù)官方文檔的原文翻譯的期贫,因為能力有限跟匆,有些地方翻譯的不好,歡迎批評指正通砍,...
    大風過崗閱讀 27,043評論 0 16
  • Guava Cache以下的特性: automatic loading of entries into the c...
    小鋤禾閱讀 8,618評論 2 11
  • LocalCache是一種很好的優(yōu)化方案玛臂,它可以成倍的提高處理效率。面對高并發(fā)的請求,響應十分可觀迹冤。如果訪問的資源...
    一只小哈閱讀 4,889評論 4 7
  • 1.let命令 let用來聲明變量.用法類似于var,但是所聲明的變量只會在let命令所在的代碼內(nèi)有效 var會發(fā)...
    林空鹿飲溪_F閱讀 264評論 0 2