Android緩存策略

老婆保佑鹉戚,代碼無BUG

前言

最近在刷面試題垫桂,遇到一個(gè)問題师幕,關(guān)于緩存的原理的,所以在這里幾個(gè)筆記诬滩,關(guān)于緩存很多大牛都說過了霹粥,我只是做個(gè)筆記,下面的很多都是網(wǎng)上查看到的碱呼,并非原創(chuàng)

目錄

  • 一:Android 緩存策略

      1. 內(nèi)存緩存(LruCache)
    • 2.磁盤緩存(文件緩存)——DiskLruCache分析
    • 3 ASimpleCache
  • 二:使用

      1. LRU使用
      1. DiskLruCache
  • 三 源碼分析


一:Android 緩存策略

1. 內(nèi)存緩存(LruCache)

LRU蒙挑,全稱Least Rencetly Used宗侦,即最近最少使用愚臀,是一種非常常用的置換算法,也即淘汰最長時(shí)間未使用的對象矾利。LRU在操作系統(tǒng)中的頁面置換算法中廣泛使用姑裂,我們的內(nèi)存或緩存空間是有限的,當(dāng)新加入一個(gè)對象時(shí)男旗,造成我們的緩存空間不足了舶斧,此時(shí)就需要根據(jù)某種算法對緩存中原有數(shù)據(jù)進(jìn)行淘汰貨刪除,而LRU選擇的是將最長時(shí)間未使用的對象進(jìn)行淘汰察皇。

2. 磁盤緩存(文件緩存)——DiskLruCache分析

JakeWharton/DiskLruCache

不同于LruCache茴厉,LruCache是將數(shù)據(jù)緩存到內(nèi)存中去泽台,而DiskLruCache是外部緩存,例如可以將網(wǎng)絡(luò)下載的圖片永久的緩存到手機(jī)外部存儲中去矾缓,并可以將緩存數(shù)據(jù)取出來使用怀酷,DiskLruCache不是google官方所寫,但是得到了官方推薦

3. ASimpleCache

ASimpleCache如何使用


二:使用

1. LRU使用

(1) 實(shí)例化

看下源碼

public class LruCache<K, V> {}

鍵值對的形式

下面是往上很多標(biāo)準(zhǔn)的寫法嗜闻,用于保存圖片

    private void init_Lru() {
        //設(shè)置LruCache緩存的大小蜕依,一般為當(dāng)前進(jìn)程可用容量的1/8。
        int maxMemory = (int) (Runtime.getRuntime().totalMemory() / 1024);
        int cacheSize = maxMemory / 8;
        // 重寫sizeOf方法琉雳,計(jì)算出要緩存的每張圖片的大小样眠。
        mMemoryCache = new LruCache<String, Bitmap>(cacheSize) {
            @Override
            protected int sizeOf(String key, Bitmap value) {
                // 重寫sizeOf方法,計(jì)算出要緩存的每張圖片的大小翠肘。
                return value.getRowBytes() * value.getHeight() / 1024;
            }
        };
}

(2)保存

 public final V put(K key, V value) {}

(3)獲取

  public final V get(K key) {}

2. DiskLruCache

(1) 權(quán)限

因?yàn)橐僮魍獠看鎯﹂苁员仨氁燃由蠙?quán)限:

<!-- 在SDCard中創(chuàng)建與刪除文件權(quán)限 -->  
<uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"/>  
<!-- 往SDCard寫入數(shù)據(jù)權(quán)限 -->  
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/> 

另外要從網(wǎng)絡(luò)下載圖片,還要加上權(quán)限:

 <uses-permission android:name="android.permission.INTERNET" />

(2)初始化DiskLruCache

public static DiskLruCache open(File directory, int appVersion, int valueCount, long maxSize){}

參數(shù)說明

  • File directory 數(shù)據(jù)的緩存地址

  • int appVersion 當(dāng)前應(yīng)用程序的版本號

  • int valueCount 指定同一個(gè)key可以對應(yīng)多少個(gè)緩存文件锯茄,基本都是傳1
  • long maxSize 定最多可以緩存多少字節(jié)的數(shù)據(jù)

獲取緩存地址的方法

    /***
     *
     * @param context
     * @param uniqueName 緩存地址的名字
     * @return 緩存地址
     */
    public File getDiskCacheDir(Context context, String uniqueName) {
        String cachePath;
        //當(dāng)SD卡存在或者SD卡不可被移除
        if (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())
                || !Environment.isExternalStorageRemovable()) {
            // 路徑/sdcard/Android/data/<application package>/cache/uniqueName
            cachePath = context.getExternalCacheDir().getPath();
        } else {
            // 路徑/data/data/<application package>/cache/uniqueName
            cachePath = context.getCacheDir().getPath();
        }
        return new File(cachePath + File.separator + uniqueName);
    }

獲取App版本

    /***
     * 
     * @param context
     * @return App 版本
     */
    public int getAppVersion(Context context) {
        try {
            PackageInfo info = context.getPackageManager().getPackageInfo(context.getPackageName(), 0);
            return info.versionCode;
        } catch (PackageManager.NameNotFoundException e) {
            e.printStackTrace();
        }
        return 1;
    }

初始化

        File test = getDiskCacheDir(this, "Bitmap");
        int appVersion = getAppVersion(this);
        long maxSize = 10 * 1024 * 1024;
        try {
            mDiskLruCache = DiskLruCache.open(test, appVersion, 1, maxSize);
        } catch (IOException e) {
            e.printStackTrace();
        }

(3) 存數(shù)據(jù)

String key = hashKeyForDisk(url);  
DiskLruCache.Editor editor = mDiskLruCache.edit(key); 
OuputStream os = editor.newOutputStream(0); 


//進(jìn)行提交才能使寫入生效
 editor.commit();
//表示放棄此次寫入
 editor.abort();

生成MD5


    public String hashKeyForDisk(String key) {
        String cacheKey;
        try {
            final MessageDigest mDigest = MessageDigest.getInstance("MD5");
            mDigest.update(key.getBytes());
            cacheKey = bytesToHexString(mDigest.digest());
        } catch (NoSuchAlgorithmException e) {
            cacheKey = String.valueOf(key.hashCode());
        }
        return cacheKey;
    }

    private String bytesToHexString(byte[] bytes) {
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < bytes.length; i++) {
            String hex = Integer.toHexString(0xFF & bytes[i]);
            if (hex.length() == 1) {
                sb.append('0');
            }
            sb.append(hex);
        }
        return sb.toString();
    }

(4)取數(shù)據(jù)

 DiskLruCache.Snapshot snapShot = mDiskLruCache.get(key);  
if (snapShot != null) {  
    InputStream is = snapShot.getInputStream(0);  
}

(5)刪除數(shù)據(jù)

public synchronized boolean remove(String key) throws IOException

(6)其他API

  • size()
    這個(gè)方法會返回當(dāng)前緩存路徑下所有緩存數(shù)據(jù)的總字節(jié)數(shù)厢塘,以byte為單位,如果應(yīng)用程序中需要在界面上顯示當(dāng)前緩存數(shù)據(jù)的總大小肌幽,就可以通過調(diào)用這個(gè)方法計(jì)算出來

  • flush()
    這個(gè)方法用于將內(nèi)存中的操作記錄同步到日志文件(也就是journal文件)當(dāng)中晚碾。這個(gè)方法非常重要,因?yàn)镈iskLruCache能夠正常工作的前提就是要依賴于journal文件中的內(nèi)容喂急。前面在講解寫入緩存操作的時(shí)候我有調(diào)用過一次這個(gè)方法格嘁,但其實(shí)并不是每次寫入緩存都要調(diào)用一次flush()方法的,頻繁地調(diào)用并不會帶來任何好處廊移,只會額外增加同步j(luò)ournal文件的時(shí)間糕簿。比較標(biāo)準(zhǔn)的做法就是在Activity的onPause()方法中去調(diào)用一次flush()方法就可以了

  • close()
    這個(gè)方法用于將DiskLruCache關(guān)閉掉,是和open()方法對應(yīng)的一個(gè)方法狡孔。關(guān)閉掉了之后就不能再調(diào)用DiskLruCache中任何操作緩存數(shù)據(jù)的方法懂诗,通常只應(yīng)該在Activity的onDestroy()方法中去調(diào)用close()方法。

  • delete()
    這個(gè)方法用于將所有的緩存數(shù)據(jù)全部刪除苗膝,比如說網(wǎng)易新聞中的那個(gè)手動(dòng)清理緩存功能殃恒,其實(shí)只需要調(diào)用一下DiskLruCache的delete()方法就可以實(shí)現(xiàn)了。


三. 源碼分析

1. LRU 源碼分析

屬性

public class LruCache<K, V> {

private int size;// 當(dāng)前大小
private int maxSize;// 最大容量

private int putCount;// put次數(shù)
private int createCount;// 創(chuàng)建次數(shù)
private int evictionCount;// 回收次數(shù)
private int hitCount;// 命中次數(shù)
private int missCount;// 未命中次數(shù)
}

構(gòu)造方法

    /**
     * @param maxSize for caches that do not override {@link #sizeOf}, this is
     *     the maximum number of entries in the cache. For all other caches,
     *     this is the maximum sum of the sizes of the entries in this cache.
     */
    public LruCache(int maxSize) {
        if (maxSize <= 0) {
            throw new IllegalArgumentException("maxSize <= 0");
        }
        this.maxSize = maxSize;
        this.map = new LinkedHashMap<K, V>(0, 0.75f, true);
    }
  • 設(shè)置了最大容量
  • 可以看到有一個(gè)LinkedHashMap辱揭,這個(gè)是核心离唐,用于儲存緩存的對象,
  • 在LinkedHashMap中的第三個(gè)參數(shù)指定為true问窃,表示這個(gè)LinkedHashMap將是基于數(shù)據(jù)的訪問順序進(jìn)行排序亥鬓。為false,則為插入順序(這里就是核心)

sizeOf && safeSizeOf

 private int safeSizeOf(K key, V value) {
        int result = sizeOf(key, value);
        if (result < 0) {
            throw new IllegalStateException("Negative size: " + key + "=" + value);
        }
        return result;
    }

    /**
     * Returns the size of the entry for {@code key} and {@code value} in
     * user-defined units.  The default implementation returns 1 so that size
     * is the number of entries and max size is the maximum number of entries.
     *
     * <p>An entry's size must not change while it is in the cache.
     */
    protected int sizeOf(K key, V value) {
        return 1;
    }
  • 由于各種數(shù)據(jù)類型大小測量的標(biāo)準(zhǔn)不統(tǒng)一域庇,具體測量的方法應(yīng)該由使用者來實(shí)現(xiàn)

保存數(shù)據(jù)

下面代碼來自 http://blog.csdn.net/shakespeare001/article/details/51695358

/**
   * 給對應(yīng)key緩存value嵌戈,并且將該value移動(dòng)到鏈表的尾部覆积。
   */
public final V put(K key, V value) {
    if (key == null || value == null) {
        throw new NullPointerException("key == null || value == null");
    }

      V previous;
      synchronized (this) {
        // 記錄 put 的次數(shù)
        putCount++;
        // 通過鍵值對,計(jì)算出要保存對象value的大小熟呛,并更新當(dāng)前緩存大小
        size += safeSizeOf(key, value);
        /*
         * 如果 之前存在key技健,用新的value覆蓋原來的數(shù)據(jù), 并返回 之前key 的value
         * 記錄在 previous
         */
        previous = map.put(key, value);
        // 如果之前存在key惰拱,并且之前的value不為null
        if (previous != null) {
            // 計(jì)算出 之前value的大小雌贱,因?yàn)榍懊鎠ize已經(jīng)加上了新的value數(shù)據(jù)的大小,此時(shí)偿短,需要再次更新size欣孤,減去原來value的大小
            size -= safeSizeOf(key, previous);
        }
      }

    // 如果之前存在key,并且之前的value不為null
    if (previous != null) {
        /*
         * previous值被剔除了昔逗,此次添加的 value 已經(jīng)作為key的 新值
         * 告訴 自定義 的 entryRemoved 方法
         */
        entryRemoved(false, key, previous, value);
    }
    //裁剪緩存容量(在當(dāng)前緩存數(shù)據(jù)大小超過了總?cè)萘縨axSize時(shí)降传,才會真正去執(zhí)行LRU)
    trimToSize(maxSize);
      return previous;
}
  • key和value判空,說明LruCache中不允許key和value為null勾怒;
  • 通過safeSizeOf()獲取要加入對象數(shù)據(jù)的大小婆排,并更新當(dāng)前緩存數(shù)據(jù)的大小笔链;
  • 將新的對象數(shù)據(jù)放入到緩存中段只,即調(diào)用LinkedHashMap的put方法,如果原來存在該key時(shí)鉴扫,直接替換掉原來的value值赞枕,并返回之前的value值,得到之前value的大小坪创,更新當(dāng)前緩存數(shù)據(jù)的size大锌簧簟;如果原來不存在該key莱预,則直接加入緩存即可柠掂;
  • 清理緩存空間,當(dāng)我們加入一個(gè)數(shù)據(jù)時(shí)(put)依沮,為了保證當(dāng)前數(shù)據(jù)的緩存所占大小沒有超過我們指定的總大小涯贞,通過調(diào)用trimToSize()來對緩存空間進(jìn)行管理控制。

trimToSize

public void trimToSize(int maxSize) {
    /*
     * 循環(huán)進(jìn)行LRU悉抵,直到當(dāng)前所占容量大小沒有超過指定的總?cè)萘看笮?     */
    while (true) {
        K key;
        V value;
        synchronized (this) {
            // 一些異常情況的處理
            if (size < 0 || (map.isEmpty() && size != 0)) {
                throw new IllegalStateException(
                        getClass().getName() + ".sizeOf() is reporting inconsistent results!");
            }
            // 首先判斷當(dāng)前緩存數(shù)據(jù)大小是否超過了指定的緩存空間總大小肩狂。如果沒有超過摘完,即緩存中還可以存入數(shù)據(jù)姥饰,直接跳出循環(huán),清理完畢
            if (size <= maxSize || map.isEmpty()) {
                break;
            }
            /**
             * 執(zhí)行到這孝治,表示當(dāng)前緩存數(shù)據(jù)已超過了總?cè)萘苛蟹啵枰獔?zhí)行LRU审磁,即將最近最少使用的數(shù)據(jù)清除掉,直到數(shù)據(jù)所占緩存空間沒有超標(biāo);
             * 根據(jù)前面的原理分析岂座,知道态蒂,在鏈表中,鏈表的頭結(jié)點(diǎn)是最近最少使用的數(shù)據(jù)费什,因此钾恢,最先清除掉鏈表前面的結(jié)點(diǎn)
             */
            Map.Entry<K, V> toEvict = map.entrySet().iterator().next();
            key = toEvict.getKey();
            value = toEvict.getValue();
            map.remove(key);
            // 移除掉后,更新當(dāng)前數(shù)據(jù)緩存的大小
            size -= safeSizeOf(key, value);
            // 更新移除的結(jié)點(diǎn)數(shù)量
            evictionCount++;
        }
        /*
         * 通知某個(gè)結(jié)點(diǎn)被移除鸳址,類似于回調(diào)
         */
        entryRemoved(true, key, value, null);
    }
}
  • trimToSize()方法的作用就是為了保證當(dāng)前數(shù)據(jù)的緩存大小不能超過我們指定的緩存總大小瘩蚪,如果超過了,就會開始移除最近最少使用的數(shù)據(jù)稿黍,直到size符合要求疹瘦。
  • trimToSize()方法在put()的時(shí)候一定會調(diào)用,在get()的時(shí)候有可能會調(diào)用巡球。

public final V get(K key) {}獲取數(shù)據(jù)

/**
 * 根據(jù)key查詢緩存言沐,如果該key對應(yīng)的value存在于緩存,直接返回value酣栈;
* 訪問到這個(gè)結(jié)點(diǎn)時(shí)险胰,LinkHashMap會將它移動(dòng)到雙向循環(huán)鏈表的的尾部。
* 如果如果沒有緩存的值矿筝,則返回null鸯乃。(如果開發(fā)者重寫了create()的話,返回創(chuàng)建的value)
*/
public final V get(K key) {
    if (key == null) {
        throw new NullPointerException("key == null");
    }

    V mapValue;
    synchronized (this) {
        // LinkHashMap 如果設(shè)置按照訪問順序的話跋涣,這里每次get都會重整數(shù)據(jù)順序
        mapValue = map.get(key);
        // 計(jì)算 命中次數(shù)
        if (mapValue != null) {
            hitCount++;
            return mapValue;
        }
        // 計(jì)算 丟失次數(shù)
        missCount++;
    }

    /*
     * 官方解釋:
     * 嘗試創(chuàng)建一個(gè)值缨睡,這可能需要很長時(shí)間,并且Map可能在create()返回的值時(shí)有所不同陈辱。如果在create()執(zhí)行的時(shí)
     * 候奖年,用這個(gè)key執(zhí)行了put方法,那么此時(shí)就發(fā)生了沖突沛贪,我們在Map中刪除這個(gè)創(chuàng)建的值陋守,釋放被創(chuàng)建的值,保留put進(jìn)去的值利赋。
     */
    V createdValue = create(key);
    if (createdValue == null) {
        return null;
    }

    /***************************
     * 不覆寫create方法走不到下面 *
     ***************************/
    /*
     * 正常情況走不到這里
     * 走到這里的話 說明 實(shí)現(xiàn)了自定義的 create(K key) 邏輯
     * 因?yàn)槟J(rèn)的 create(K key) 邏輯為null
     */
    synchronized (this) {
        // 記錄 create 的次數(shù)
        createCount++;
        // 將自定義create創(chuàng)建的值水评,放入LinkedHashMap中,如果key已經(jīng)存在媚送,會返回 之前相同key 的值
        mapValue = map.put(key, createdValue);

        // 如果之前存在相同key的value中燥,即有沖突。
        if (mapValue != null) {
            /*
             * 有沖突
             * 所以 撤銷 剛才的 操作
             * 將 之前相同key 的值 重新放回去
             */
            map.put(key, mapValue);
        } else {
            // 拿到鍵值對塘偎,計(jì)算出在容量中的相對長度疗涉,然后加上
            size += safeSizeOf(key, createdValue);
        }
    }

    // 如果上面 判斷出了 將要放入的值發(fā)生沖突
    if (mapValue != null) {
        /*
         * 剛才create的值被刪除了拿霉,原來的 之前相同key 的值被重新添加回去了
         * 告訴 自定義 的 entryRemoved 方法
         */
        entryRemoved(false, key, createdValue, mapValue);
        return mapValue;
    } else {
        // 上面 進(jìn)行了 size += 操作 所以這里要重整長度
        trimToSize(maxSize);
        return createdValue;
    }
}
  • 先嘗試從map緩存中獲取value,即mapVaule = map.get(key);如果mapVaule != null咱扣,說明緩存中存在該對象绽淘,直接返回即可;
  • 如果mapVaule == null闹伪,說明緩存中不存在該對象沪铭,大多數(shù)情況下會直接返回null;但是如果我們重寫了create()方法偏瓤,在緩存沒有該數(shù)據(jù)的時(shí)候自己去創(chuàng)建一個(gè)伦意,則會繼續(xù)往下走,中間可能會出現(xiàn)沖突硼补,看注釋驮肉;
  • 注意:在我們通過LinkedHashMap進(jìn)行g(shù)et(key)或put(key,value)時(shí)都會對鏈表進(jìn)行調(diào)整,即將剛剛訪問get或加入put的結(jié)點(diǎn)放入到鏈表尾部已骇。

entryRemoved()

/**
* 1.當(dāng)被回收或者刪掉時(shí)調(diào)用离钝。該方法當(dāng)value被回收釋放存儲空間時(shí)被remove調(diào)用
* 或者替換條目值時(shí)put調(diào)用,默認(rèn)實(shí)現(xiàn)什么都沒做褪储。
* 2.該方法沒用同步調(diào)用卵渴,如果其他線程訪問緩存時(shí),該方法也會執(zhí)行鲤竹。
* 3.evicted=true:如果該條目被刪除空間 (表示 進(jìn)行了trimToSize or remove)  evicted=false:put沖突后 或 get里成功create后
* 導(dǎo)致
* 4.newValue!=null浪读,那么則被put()或get()調(diào)用。
*/
protected void entryRemoved(boolean evicted, K key, V oldValue, V newValue) {
}
  • 可以發(fā)現(xiàn)entryRemoved方法是一個(gè)空方法辛藻,說明這個(gè)也是讓開發(fā)者自己根據(jù)需求去重寫的碘橘。
  • entryRemoved()主要作用就是在結(jié)點(diǎn)數(shù)據(jù)value需要被刪除或回收的時(shí)候,給開發(fā)者的回調(diào)吱肌。開發(fā)者就可以在這個(gè)方法里面實(shí)現(xiàn)一些自己的邏輯:
    • 可以進(jìn)行資源的回收痘拆;
    • 可以實(shí)現(xiàn)二級內(nèi)存緩存,可以進(jìn)一步提高性能氮墨,思路如下:

二級緩存

  • 重寫LruCache的entryRemoved()函數(shù)纺蛆,
  • 把刪除掉的item,再次存入另外一個(gè)LinkedHashMap<String, SoftWeakReference<Bitmap>>
  • 這個(gè)數(shù)據(jù)結(jié)構(gòu)當(dāng)做二級緩存规揪,每次獲得圖片的時(shí)候桥氏,先判斷LruCache中是否緩存,沒有的話猛铅,再判斷這個(gè)二級緩存中是否有字支,如果都沒有再從sdcard上獲取。sdcard上也沒有的話,就從網(wǎng)絡(luò)服務(wù)器上拉取祥款。

entryRemoved()在LruCache中有四個(gè)地方進(jìn)行了調(diào)用:put()、get()月杉、trimToSize()刃跛、remove()中進(jìn)行了調(diào)用。


2. DiskLruCache

Android DiskLruCache 源碼解析 硬盤緩存的絕佳方案


原文地址

http://blog.csdn.net/shakespeare001/article/details/51695358

https://www.tuicool.com/articles/JB7RNj

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末苛萎,一起剝皮案震驚了整個(gè)濱河市桨昙,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌腌歉,老刑警劉巖蛙酪,帶你破解...
    沈念sama閱讀 211,817評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異翘盖,居然都是意外死亡桂塞,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,329評論 3 385
  • 文/潘曉璐 我一進(jìn)店門馍驯,熙熙樓的掌柜王于貴愁眉苦臉地迎上來阁危,“玉大人,你說我怎么就攤上這事汰瘫】翊颍” “怎么了?”我有些...
    開封第一講書人閱讀 157,354評論 0 348
  • 文/不壞的土叔 我叫張陵混弥,是天一觀的道長趴乡。 經(jīng)常有香客問我,道長蝗拿,這世上最難降的妖魔是什么晾捏? 我笑而不...
    開封第一講書人閱讀 56,498評論 1 284
  • 正文 為了忘掉前任,我火速辦了婚禮哀托,結(jié)果婚禮上粟瞬,老公的妹妹穿的比我還像新娘。我一直安慰自己萤捆,他們只是感情好裙品,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,600評論 6 386
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著俗或,像睡著了一般市怎。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上辛慰,一...
    開封第一講書人閱讀 49,829評論 1 290
  • 那天区匠,我揣著相機(jī)與錄音,去河邊找鬼。 笑死驰弄,一個(gè)胖子當(dāng)著我的面吹牛麻汰,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播戚篙,決...
    沈念sama閱讀 38,979評論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼五鲫,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了岔擂?” 一聲冷哼從身側(cè)響起位喂,我...
    開封第一講書人閱讀 37,722評論 0 266
  • 序言:老撾萬榮一對情侶失蹤谬返,失蹤者是張志新(化名)和其女友劉穎拴签,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體惹苗,經(jīng)...
    沈念sama閱讀 44,189評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡痛倚,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,519評論 2 327
  • 正文 我和宋清朗相戀三年规婆,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片蝉稳。...
    茶點(diǎn)故事閱讀 38,654評論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡聋呢,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出颠区,到底是詐尸還是另有隱情削锰,我是刑警寧澤,帶...
    沈念sama閱讀 34,329評論 4 330
  • 正文 年R本政府宣布毕莱,位于F島的核電站器贩,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏朋截。R本人自食惡果不足惜蛹稍,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,940評論 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望部服。 院中可真熱鬧唆姐,春花似錦、人聲如沸廓八。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,762評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽剧蹂。三九已至声功,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間宠叼,已是汗流浹背先巴。 一陣腳步聲響...
    開封第一講書人閱讀 31,993評論 1 266
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人伸蚯。 一個(gè)月前我還...
    沈念sama閱讀 46,382評論 2 360
  • 正文 我出身青樓摩渺,卻偏偏與公主長得像,于是被迫代替她去往敵國和親剂邮。 傳聞我的和親對象是個(gè)殘疾皇子摇幻,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,543評論 2 349

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