Caffeine Cache 進(jìn)程緩存之王

1. 前言

互聯(lián)網(wǎng)軟件神速發(fā)展诡右,用戶的體驗(yàn)度是判斷一個(gè)軟件好壞的重要原因,所以緩存就是必不可少的一個(gè)神器诺祸。在多線程高并發(fā)場(chǎng)景中往往是離不開(kāi)cache的揣钦,需要根據(jù)不同的應(yīng)用場(chǎng)景來(lái)需要選擇不同的cache获询,比如分布式緩存如redis涨岁、memcached,還有本地(進(jìn)程內(nèi))緩存如ehcache吉嚣、GuavaCache梢薪、Caffeine。

說(shuō)起Guava Cache尝哆,很多人都不會(huì)陌生秉撇,它是Google Guava工具包中的一個(gè)非常方便易用的本地化緩存實(shí)現(xiàn),基于LRU算法實(shí)現(xiàn),支持多種緩存過(guò)期策略琐馆。由于Guava的大量使用规阀,Guava Cache也得到了大量的應(yīng)用。但是瘦麸,Guava Cache的性能一定是最好的嗎谁撼?也許,曾經(jīng)滋饲,它的性能是非常不錯(cuò)的厉碟。但所謂長(zhǎng)江后浪推前浪,總會(huì)有更加優(yōu)秀的技術(shù)出現(xiàn)屠缭。今天箍鼓,我就來(lái)介紹一個(gè)比Guava Cache性能更高的緩存框架:Caffeine。

2. 比較

Google Guava工具包中的一個(gè)非常方便易用的本地化緩存實(shí)現(xiàn)呵曹,基于LRU算法實(shí)現(xiàn)款咖,支持多種緩存過(guò)期策略。

EhCache 是一個(gè)純Java的進(jìn)程內(nèi)緩存框架逢并,具有快速之剧、精干等特點(diǎn),是Hibernate中默認(rèn)的CacheProvider砍聊。

Caffeine是使用Java8對(duì)Guava緩存的重寫(xiě)版本背稼,在Spring Boot 2.0中將取代,基于LRU算法實(shí)現(xiàn)玻蝌,支持多種緩存過(guò)期策略蟹肘。

2.1 官方性能比較

場(chǎng)景1:8個(gè)線程讀,100%的讀操作

場(chǎng)景二:6個(gè)線程讀俯树,2個(gè)線程寫(xiě)帘腹,也就是75%的讀操作,25%的寫(xiě)操作

場(chǎng)景三:8個(gè)線程寫(xiě)许饿,100%的寫(xiě)操作

可以清楚的看到Caffeine效率明顯的高于其他緩存阳欲。

3. 如何使用

public static void main(String[] args) {
      LoadingCache<String, String> build = CacheBuilder.newBuilder().initialCapacity(1).maximumSize(100).expireAfterWrite(1, TimeUnit.DAYS)
          .build(new CacheLoader<String, String>() {
             //默認(rèn)的數(shù)據(jù)加載實(shí)現(xiàn),當(dāng)調(diào)用get取值的時(shí)候陋率,如果key沒(méi)有對(duì)應(yīng)的值球化,就調(diào)用這個(gè)方法進(jìn)行加載
             @Override
             public String load(String key)  {
                  return "";
             }
         });
}

參數(shù)方法

  • initialCapacity(1) 初始緩存長(zhǎng)度為1
  • maximumSize(100) 最大長(zhǎng)度為100
  • expireAfterWrite(1, TimeUnit.DAYS) 設(shè)置緩存策略在1天未寫(xiě)入過(guò)期緩存(后面講緩存策略)

4. 過(guò)期策略

在Caffeine中分為兩種緩存,一個(gè)是有界緩存瓦糟,一個(gè)是無(wú)界緩存筒愚,無(wú)界緩存不需要過(guò)期并且沒(méi)有界限。在有界緩存中提供了三個(gè)過(guò)期API:

  • expireAfterWrite:代表著寫(xiě)了之后多久過(guò)期菩浙。(上面列子就是這種方式)
  • expireAfterAccess: 代表著最后一次訪問(wèn)了之后多久過(guò)期巢掺。
  • expireAfter:在expireAfter中需要自己實(shí)現(xiàn)Expiry接口句伶,這個(gè)接口支持create,update,以及access了之后多久過(guò)期。注意這個(gè)API和前面兩個(gè)API是互斥的陆淀。這里和前面兩個(gè)API不同的是考余,需要你告訴緩存框架,他應(yīng)該在具體的某個(gè)時(shí)間過(guò)期倔约,也就是通過(guò)前面的重寫(xiě)create,update,以及access的方法秃殉,獲取具體的過(guò)期時(shí)間。

4. 更新策略

何為更新策略浸剩?就是在設(shè)定多長(zhǎng)時(shí)間后會(huì)自動(dòng)刷新緩存钾军。

Caffeine提供了refreshAfterWrite()方法來(lái)讓我們進(jìn)行寫(xiě)后多久更新策略:

LoadingCache<String, String> build = CacheBuilder.newBuilder().refreshAfterWrite(1, TimeUnit.DAYS)
   .build(new CacheLoader<String, String>() {
          @Override
          public String load(String key)  {
             return "";
          }
    });
}

上面的代碼我們需要建立一個(gè)CacheLodaer來(lái)進(jìn)行刷新,這里是同步進(jìn)行的,可以通過(guò)buildAsync方法進(jìn)行異步構(gòu)建绢要。在實(shí)際業(yè)務(wù)中這里可以把我們代碼中的mapper傳入進(jìn)去吏恭,進(jìn)行數(shù)據(jù)源的刷新。

但是實(shí)際使用中重罪,你設(shè)置了一天刷新樱哼,但是一天后你發(fā)現(xiàn)緩存并沒(méi)有刷新。這是因?yàn)楸赜性?天后這個(gè)緩存再次訪問(wèn)才能刷新剿配,如果沒(méi)人訪問(wèn)搅幅,那么永遠(yuǎn)也不會(huì)刷新。你明白了嗎呼胚?

我們來(lái)看看自動(dòng)刷新他是怎么做的呢茄唐?自動(dòng)刷新只存在讀操作之后,也就是我們afterRead()這個(gè)方法蝇更,其中有個(gè)方法叫refreshIfNeeded沪编,他會(huì)根據(jù)你是同步還是異步然后進(jìn)行刷新處理。

5. 填充策略(Population)

Caffeine 為我們提供了三種填充策略:手動(dòng)年扩、同步和異步

5.1 手動(dòng)加載(Manual)

// 初始化緩存
Cache<String, Object> manualCache = Caffeine.newBuilder()
           .expireAfterWrite(10, TimeUnit.MINUTES)
           .maximumSize(10_000)
           .build();

String key = "name1";
// 根據(jù)key查詢一個(gè)緩存蚁廓,如果沒(méi)有返回NULL
graph = manualCache.getIfPresent(key);
// 如果緩存中不存在該鍵,createExpensiveGraph函數(shù)將用于提供回退值厨幻,該值在計(jì)算后插入緩存中
graph = manualCache.get(key, k -> createExpensiveGraph(k));
// 使用 put 方法手動(dòng)填充緩存相嵌,如果以前有值就覆蓋以前的值
manualCache.put(key, graph);
// 刪除一個(gè)緩存
manualCache.invalidate(key);

ConcurrentMap<String, Object> map = manualCache.asMap();
cache.invalidate(key);

Cache接口允許顯式的去控制緩存的檢索,更新和刪除况脆。

我們可以通過(guò)cache.getIfPresent(key) 方法來(lái)獲取一個(gè)key的值平绩,通過(guò)cache.put(key, value)方法顯示的將數(shù)控放入緩存,但是這樣子會(huì)覆蓋緩原來(lái)key的數(shù)據(jù)漠另。更加建議使用cache.get(key,k - > value) 的方式跃赚,get 方法將一個(gè)參數(shù)為 key 的 Function (createExpensiveGraph) 作為參數(shù)傳入笆搓。如果緩存中不存在該鍵性湿,則調(diào)用這個(gè) Function 函數(shù),并將返回值作為該緩存的值插入緩存中满败。get 方法是以阻塞方式執(zhí)行調(diào)用肤频,即使多個(gè)線程同時(shí)請(qǐng)求該值也只會(huì)調(diào)用一次Function方法。這樣可以避免與其他線程的寫(xiě)入競(jìng)爭(zhēng)算墨,這也是為什么使用 get 優(yōu)于 getIfPresent 的原因宵荒。

注意:如果調(diào)用該方法返回NULL(如上面的 createExpensiveGraph 方法),則cache.get返回null净嘀,如果調(diào)用該方法拋出異常报咳,則get方法也會(huì)拋出異常⊥诓兀可以使用Cache.asMap() 方法獲取ConcurrentMap進(jìn)而對(duì)緩存進(jìn)行一些更改暑刃。

5.2 同步加載(Loading)

// 初始化緩存
LoadingCache<String, Object> loadingCache = Caffeine.newBuilder()
     .maximumSize(10_000)
     .expireAfterWrite(10, TimeUnit.MINUTES)
     .build(key -> createExpensiveGraph(key));

String key = "name1";
// 采用同步方式去獲取一個(gè)緩存和上面的手動(dòng)方式是一個(gè)原理。在build Cache的時(shí)候會(huì)提供一個(gè)createExpensiveGraph函數(shù)膜眠。
// 查詢并在缺失的情況下使用同步的方式來(lái)構(gòu)建一個(gè)緩存
Object graph = loadingCache.get(key);

// 獲取組key的值返回一個(gè)Map
List<String> keys = new ArrayList<>();
keys.add(key);
Map<String, Object> graphs = loadingCache.getAll(keys);

LoadingCache是使用CacheLoader來(lái)構(gòu)建的緩存的值岩臣。批量查找可以使用getAll方法,默認(rèn)情況下宵膨,getAll將會(huì)對(duì)緩存中沒(méi)有值的key分別調(diào)用CacheLoader.load方法來(lái)構(gòu)建緩存的值架谎。我們可以重寫(xiě)CacheLoader.loadAll方法來(lái)提高getAll的效率。

注意:您可以編寫(xiě)一個(gè)CacheLoader.loadAll來(lái)實(shí)現(xiàn)為特別請(qǐng)求的key加載值辟躏。例如谷扣,如果計(jì)算某個(gè)組中的任何鍵的值將為該組中的所有鍵提供值,則loadAll可能會(huì)同時(shí)加載該組的其余部分鸿脓。

5.3 異步加載(Asynchronously Loading)

AsyncLoadingCache<String, Object> asyncLoadingCache = Caffeine.newBuilder()
        .maximumSize(10_000)
        .expireAfterWrite(10, TimeUnit.MINUTES)
        // Either: Build with a synchronous computation that is wrapped as asynchronous
        .buildAsync(key -> createExpensiveGraph(key));
        // Or: Build with a asynchronous computation that returns a future
        // .buildAsync((key, executor) -> createExpensiveGraphAsync(key, executor));

String key = "name1";

// 查詢并在缺失的情況下使用異步的方式來(lái)構(gòu)建緩存
CompletableFuture<Object> graph = asyncLoadingCache.get(key);
// 查詢一組緩存并在缺失的情況下使用異步的方式來(lái)構(gòu)建緩存
List<String> keys = new ArrayList<>();
keys.add(key);
CompletableFuture<Map<String, Object>> graphs = asyncLoadingCache.getAll(keys);
// 異步轉(zhuǎn)同步
loadingCache = asyncLoadingCache.synchronous();

AsyncLoadingCache是繼承自LoadingCache類的抑钟,異步加載使用Executor去調(diào)用方法并返回一個(gè)CompletableFuture。異步加載緩存使用了響應(yīng)式編程模型野哭。

如果要以同步方式調(diào)用時(shí)在塔,應(yīng)提供CacheLoader。要以異步表示時(shí)拨黔,應(yīng)該提供一個(gè)AsyncCacheLoader蛔溃,并返回一個(gè)CompletableFuture。

synchronous()這個(gè)方法返回了一個(gè)LoadingCacheView視圖篱蝇,LoadingCacheView也繼承自LoadingCache贺待。調(diào)用該方法后就相當(dāng)于你將一個(gè)異步加載的緩存AsyncLoadingCache轉(zhuǎn)換成了一個(gè)同步加載的緩存LoadingCache。

默認(rèn)使用ForkJoinPool.commonPool()來(lái)執(zhí)行異步線程零截,但是我們可以通過(guò)Caffeine.executor(Executor) 方法來(lái)替換線程池麸塞。

6. 驅(qū)逐策略(eviction)

緩存的驅(qū)逐策略是為了預(yù)測(cè)哪些數(shù)據(jù)在短期內(nèi)最可能被再次用到,從而提升緩存的命中率涧衙。LRU(Least Recently Used)策略或許是最流行的驅(qū)逐策略哪工。但LRU通過(guò)歷史數(shù)據(jù)來(lái)預(yù)測(cè)未來(lái)是局限的奥此,它會(huì)認(rèn)為最后到來(lái)的數(shù)據(jù)是最可能被再次訪問(wèn)的,從而給與它最高的優(yōu)先級(jí)雁比。

Caffeine提供三類驅(qū)逐策略:基于大兄苫ⅰ(size-based),基于時(shí)間(time-based)和基于引用(reference-based)偎捎。

6.1 基于大写乐铡(size-based)

基于大小驅(qū)逐,有兩種方式:一種是基于緩存大小茴她,一種是基于權(quán)重寻拂。

// 根據(jù)緩存的計(jì)數(shù)進(jìn)行驅(qū)逐
LoadingCache<Key, Graph> graphs = Caffeine.newBuilder()
    .maximumSize(10_000)
    .build(key -> createExpensiveGraph(key));

// 根據(jù)緩存的權(quán)重來(lái)進(jìn)行驅(qū)逐(權(quán)重只是用于確定緩存大小,不會(huì)用于決定該緩存是否被驅(qū)逐)
LoadingCache<Key, Graph> graphs = Caffeine.newBuilder()
    .maximumWeight(10_000)
    .weigher((Key key, Graph graph) -> graph.vertices().size())
    .build(key -> createExpensiveGraph(key));

我們可以使用Caffeine.maximumSize(long)方法來(lái)指定緩存的最大容量败京。當(dāng)緩存超出這個(gè)容量的時(shí)候兜喻,會(huì)使用Window TinyLfu策略來(lái)刪除緩存。我們也可以使用權(quán)重的策略來(lái)進(jìn)行驅(qū)逐赡麦,可以使用Caffeine.weigher(Weigher) 函數(shù)來(lái)指定權(quán)重朴皆,使用Caffeine.maximumWeight(long) 函數(shù)來(lái)指定緩存最大權(quán)重值。

讓我們看看如何計(jì)算緩存中的對(duì)象泛粹。當(dāng)緩存初始化時(shí)遂铡,其大小等于零:

LoadingCache<String, DataObject> cache = Caffeine.newBuilder()
                      .maximumSize(1)
                      .build(k -> DataObject.get("Data for " + k));    
assertEquals(0, cache.estimatedSize()); 

當(dāng)我們添加一個(gè)值時(shí),大小明顯增加:

cache.get("A");    
assertEquals(1, cache.estimatedSize()); 

我們可以將第二個(gè)值添加到緩存中晶姊,這導(dǎo)致第一個(gè)值被刪除:

cache.get("B"); 
assertEquals(1, cache.estimatedSize()); 

注意:maximumWeight與maximumSize不可以同時(shí)使用扒接。

6.2 基于時(shí)間(Time-based)

// 基于固定的到期策略進(jìn)行退出
LoadingCache<Key, Graph> graphs = Caffeine.newBuilder()
      .expireAfterAccess(5, TimeUnit.MINUTES)
      .build(key -> createExpensiveGraph(key));
LoadingCache<Key, Graph> graphs = Caffeine.newBuilder()
      .expireAfterWrite(10, TimeUnit.MINUTES)
      .build(key -> createExpensiveGraph(key));

// 要初始化自定義策略,我們需要實(shí)現(xiàn) Expiry 接口
LoadingCache<Key, Graph> graphs = Caffeine.newBuilder()
      .expireAfter(new Expiry<Key, Graph>() {
          @Override
          public long expireAfterCreate(Key key, Graph graph, long currentTime) {
            // Use wall clock time, rather than nanotime, if from an external resource
            long seconds = graph.creationDate().plusHours(5)
                   .minus(System.currentTimeMillis(), MILLIS)
                   .toEpochSecond();
            return TimeUnit.SECONDS.toNanos(seconds);
         }

          @Override
          public long expireAfterUpdate(Key key, Graph graph, 
            long currentTime, long currentDuration) {
            return currentDuration;
          }

          @Override
          public long expireAfterRead(Key key, Graph graph,
             long currentTime, long currentDuration) {
             return currentDuration;
          }
      })
      .build(key -> createExpensiveGraph(key));

6.3 基于引用(reference-based)

強(qiáng)引用们衙,軟引用钾怔,弱引用概念說(shuō)明請(qǐng)點(diǎn)擊連接,這里說(shuō)一下各各引用的區(qū)別:

// 當(dāng)key和value都沒(méi)有引用時(shí)驅(qū)逐緩存
LoadingCache<Key, Graph> graphs = Caffeine.newBuilder()
                                          .weakKeys()
                                          .weakValues()
                                          .build(key -> createExpensiveGraph(key));

// 當(dāng)垃圾收集器需要釋放內(nèi)存時(shí)驅(qū)逐
LoadingCache<Key, Graph> graphs = Caffeine.newBuilder()
                                          .softValues()
                                          .build(key -> createExpensiveGraph(key));

我們可以將緩存的驅(qū)逐配置成基于垃圾回收器蒙挑。當(dāng)沒(méi)有任何對(duì)對(duì)象的強(qiáng)引用時(shí)宗侦,使用 WeakRefence 可以啟用對(duì)象的垃圾收回收。SoftReference 允許對(duì)象根據(jù) JVM 的全局最近最少使用(Least-Recently-Used)的策略進(jìn)行垃圾回收忆蚀。

注意:AsyncLoadingCache不支持弱引用和軟引用矾利。

7. 移除監(jiān)聽(tīng)器(Removal)

如果我們需要在緩存被移除的時(shí)候,得到通知產(chǎn)生回調(diào)馋袜,并做一些額外處理工作男旗。這個(gè)時(shí)候RemovalListener就派上用場(chǎng)了。

7.1 概念

驅(qū)逐(eviction):由于滿足了某種驅(qū)逐策略欣鳖,后臺(tái)自動(dòng)進(jìn)行的刪除操作
無(wú)效(invalidation):表示由調(diào)用方手動(dòng)刪除緩存
移除(removal):監(jiān)聽(tīng)驅(qū)逐或無(wú)效操作的監(jiān)聽(tīng)器
手動(dòng)刪除緩存:在任何時(shí)候察皇,您都可能明確地使緩存無(wú)效,而不用等待緩存被驅(qū)逐泽台。

// individual key
cache.invalidate(key)
// bulk keys
cache.invalidateAll(keys)
// all keys
cache.invalidateAll()

7.2 Removal 監(jiān)聽(tīng)器

Cache<Key, Graph> graphs = Caffeine.newBuilder()
    .removalListener((Key key, Graph graph, RemovalCause cause) ->
        System.out.printf("Key %s was removed (%s)%n", key, cause))
    .build();

您可以通過(guò)Caffeine.removalListener(RemovalListener) 為緩存指定一個(gè)刪除偵聽(tīng)器什荣,以便在刪除數(shù)據(jù)時(shí)執(zhí)行某些操作呀忧。 RemovalListener可以獲取到key、value和RemovalCause(刪除的原因)溃睹。

刪除偵聽(tīng)器的里面的操作是使用Executor來(lái)異步執(zhí)行的。默認(rèn)執(zhí)行程序是ForkJoinPool.commonPool()胰坟,可以通過(guò)Caffeine.executor(Executor)覆蓋因篇。當(dāng)操作必須與刪除同步執(zhí)行時(shí),請(qǐng)改為使用CacheWrite笔横,CacheWrite將在下面說(shuō)明竞滓。

注意:由RemovalListener拋出的任何異常都會(huì)被記錄(使用Logger)并不會(huì)拋出。

7.3 移除監(jiān)聽(tīng)器應(yīng)用

public class Main {  
  
    // 創(chuàng)建一個(gè)監(jiān)聽(tīng)器  
    private static class MyRemovalListener implements RemovalListener<Integer, Integer> {  
    @Override  
    public void onRemoval(RemovalNotification<Integer, Integer> notification) {  
        String tips = String.format("key=%s,value=%s,reason=%s", notification.getKey(), notification.getValue(), notification.getCause());  
        System.out.println(tips);  
    }  
    }  
  
    public static void main(String[] args) {  
  
    // 創(chuàng)建一個(gè)帶有RemovalListener監(jiān)聽(tīng)的緩存  
    Cache<Integer, Integer> cache = CacheBuilder.newBuilder().removalListener(new MyRemovalListener()).build();  
  
    cache.put(1, 1);  
  
    // 手動(dòng)清除  
    cache.invalidate(1);  
  
    System.out.println(cache.getIfPresent(1)); // null  
    }  
  
}  

使用invalidate()清除緩存數(shù)據(jù)之后吹缔,注冊(cè)的回調(diào)被觸發(fā)了

8. 統(tǒng)計(jì)(Statistics)

Cache<Key, Graph> graphs = Caffeine.newBuilder()
      .maximumSize(10_000)
      .recordStats()
      .build();

使用Caffeine.recordStats()商佑,您可以打開(kāi)統(tǒng)計(jì)信息收集。Cache.stats() 方法返回提供統(tǒng)計(jì)信息的CacheStats厢塘,如:

  • hitRate():返回命中與請(qǐng)求的比率
  • hitCount(): 返回命中緩存的總數(shù)
  • evictionCount():緩存逐出的數(shù)量
  • averageLoadPenalty():加載新值所花費(fèi)的平均時(shí)間
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末茶没,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子晚碾,更是在濱河造成了極大的恐慌抓半,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,123評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件格嘁,死亡現(xiàn)場(chǎng)離奇詭異笛求,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)糕簿,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,031評(píng)論 2 384
  • 文/潘曉璐 我一進(jìn)店門(mén)探入,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人懂诗,你說(shuō)我怎么就攤上這事蜂嗽。” “怎么了响禽?”我有些...
    開(kāi)封第一講書(shū)人閱讀 156,723評(píng)論 0 345
  • 文/不壞的土叔 我叫張陵徒爹,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我芋类,道長(zhǎng)隆嗅,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,357評(píng)論 1 283
  • 正文 為了忘掉前任侯繁,我火速辦了婚禮胖喳,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘贮竟。我一直安慰自己丽焊,他們只是感情好较剃,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,412評(píng)論 5 384
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著技健,像睡著了一般写穴。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上雌贱,一...
    開(kāi)封第一講書(shū)人閱讀 49,760評(píng)論 1 289
  • 那天啊送,我揣著相機(jī)與錄音,去河邊找鬼欣孤。 笑死馋没,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的降传。 我是一名探鬼主播篷朵,決...
    沈念sama閱讀 38,904評(píng)論 3 405
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼婆排!你這毒婦竟也來(lái)了声旺?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 37,672評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤泽论,失蹤者是張志新(化名)和其女友劉穎艾少,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體翼悴,經(jīng)...
    沈念sama閱讀 44,118評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡缚够,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,456評(píng)論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了鹦赎。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片谍椅。...
    茶點(diǎn)故事閱讀 38,599評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖古话,靈堂內(nèi)的尸體忽然破棺而出雏吭,到底是詐尸還是另有隱情,我是刑警寧澤陪踩,帶...
    沈念sama閱讀 34,264評(píng)論 4 328
  • 正文 年R本政府宣布杖们,位于F島的核電站,受9級(jí)特大地震影響肩狂,放射性物質(zhì)發(fā)生泄漏摘完。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,857評(píng)論 3 312
  • 文/蒙蒙 一傻谁、第九天 我趴在偏房一處隱蔽的房頂上張望孝治。 院中可真熱鬧,春花似錦、人聲如沸谈飒。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,731評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)杭措。三九已至费什,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間手素,已是汗流浹背吕喘。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,956評(píng)論 1 264
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留刑桑,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,286評(píng)論 2 360
  • 正文 我出身青樓募舟,卻偏偏與公主長(zhǎng)得像祠斧,于是被迫代替她去往敵國(guó)和親泉褐。 傳聞我的和親對(duì)象是個(gè)殘疾皇子猫十,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,465評(píng)論 2 348

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

  • 簡(jiǎn)介 在本文中,我們來(lái)看看 Caffeine — 一個(gè)高性能的 Java 緩存庫(kù)浦妄。 緩存和 Map 之間的一個(gè)根本...
    xiaolyuh閱讀 71,117評(píng)論 5 48
  • 1.確認(rèn)是否需要緩存 在使用緩存之前,需要確認(rèn)你的項(xiàng)目是否真的需要緩存鸯乃。使用緩存會(huì)引入的一定的技術(shù)復(fù)雜度鲸阻,后文也將...
    Java黎先生閱讀 7,357評(píng)論 2 22
  • 俗話說(shuō)得好奖年,工欲善其事细诸,必先利其器,有了好的工具肯定得知道如何用好這些工具陋守,本篇將分為如下幾個(gè)方面介紹如何利用好緩...
    高級(jí)java架構(gòu)師閱讀 1,039評(píng)論 0 2
  • 想念兒時(shí)的桃樹(shù)震贵,我慈愛(ài)的爺爺奶奶。 清明夜半風(fēng)含悲水评, 想親念友思緒微猩系, 人如螻蟻百年修, 行走路上婉轉(zhuǎn)回之碗。 生命不...
    薔薇紫閱讀 228評(píng)論 0 1
  • 人其實(shí)從娘胎里就已形成了自己的性格特征蝙眶,后天的成長(zhǎng)環(huán)境只能對(duì)其促進(jìn)或微調(diào)修正,所以在教育孩子上,順其自然比用力過(guò)猛...
    英特閱讀 427評(píng)論 0 1