哈希表

有兩個(gè)字典愕难,分別存有 100 條數(shù)據(jù)和 10000 條數(shù)據(jù)个榕,如果用一個(gè)不存在的 key 去查找數(shù)據(jù)坯墨,在哪個(gè)字典中速度更快?

有些計(jì)算機(jī)常識(shí)的讀者都會(huì)立刻回答:“一樣快权逗,底層都用了哈希表美尸,查找的時(shí)間復(fù)雜度為 O(1)”。然而實(shí)際情況真的是這樣么斟薇?

答案是否定的师坎,存在少部分情況兩者速度不一致,本文首先對(duì)哈希表做一個(gè)簡(jiǎn)短的總結(jié)堪滨,然后思考 Java 和 Redis 中對(duì)哈希表的實(shí)現(xiàn)胯陋,最后再得出結(jié)論。

1. 介紹

散列表(Hash table袱箱,也叫哈希表)遏乔,是根據(jù)鍵值對(duì)(Key value)而直接進(jìn)行訪問(wèn)的數(shù)據(jù)結(jié)構(gòu)。也就是說(shuō)发笔,它通過(guò)把鍵值對(duì)映射到表中一個(gè)位置來(lái)訪問(wèn)記錄盟萨,以加快查找的速度。這個(gè)映射函數(shù)叫做散列函數(shù)了讨,存放記錄的數(shù)組叫做哈希表鸯旁。

給定表 M,存在函數(shù) f(key)量蕊,對(duì)任意給定的鍵值 key铺罢,代入函數(shù)后若能得到包含該關(guān)鍵字的記錄在表中的地址,則稱表 M 為哈希(Hash)表残炮,函數(shù) f(key) 為哈希(Hash) 函數(shù)韭赘。

鏈表查找可能會(huì)從鏈表的頭結(jié)點(diǎn)開始遍歷,依次將每個(gè)結(jié)點(diǎn)中的信息與查詢條件進(jìn)行比較势就,直到查找成功或者失敗為止泉瞻,這種做法的時(shí)間復(fù)雜度為 O(n)。即使采用二叉排序樹進(jìn)行存儲(chǔ)苞冯,也最多為 O(logn)袖牙。Hash 表通過(guò)查詢條件可以直接獲取到該記錄在表中的存儲(chǔ)位置,復(fù)雜度直接降到 O(1)舅锄。

Hash 表采用一個(gè)映射函數(shù) f : key —> address 將關(guān)鍵字映射到該記錄在表中的存儲(chǔ)位置鞭达,從而在想要查找該記錄時(shí),可以直接根據(jù)關(guān)鍵字和映射關(guān)系計(jì)算出該記錄在表中的存儲(chǔ)位置,通常情況下畴蹭,這種映射關(guān)系稱作為 Hash 函數(shù)坦仍,而通過(guò) Hash 函數(shù)和關(guān)鍵字計(jì)算出來(lái)的存儲(chǔ)位置(注意這里的存儲(chǔ)位置只是表中的存儲(chǔ)位置,并不是實(shí)際的物理地址)稱作為 Hash 地址叨襟。

2.概述

Objective-C 中的字典 NSDictionary 底層其實(shí)是一個(gè)哈希表繁扎,實(shí)際上絕大多數(shù)語(yǔ)言中字典都通過(guò)哈希表實(shí)現(xiàn),比如 Swift 字典的實(shí)現(xiàn)原理糊闽。

在討論哈希表之前梳玫,先規(guī)范幾個(gè)接下來(lái)會(huì)用到的概念。哈希表的本質(zhì)是一個(gè)數(shù)組右犹,數(shù)組中每一個(gè)元素稱為一個(gè)箱子(bin)汽纠,箱子中存放的是鍵值對(duì)。[{}, {}]

哈希表的存儲(chǔ)過(guò)程如下:

?①傀履、根據(jù) key 計(jì)算出它的哈希值 h。

?②莉炉、假設(shè)箱子的個(gè)數(shù)為 n钓账,那么這個(gè)鍵值對(duì)應(yīng)該放在第 (h % n) 個(gè)箱子中。

?③絮宁、如果該箱子中已經(jīng)有了鍵值對(duì)梆暮,就使用開放尋址法或者拉鏈法解決沖突。

在使用拉鏈法解決哈希沖突時(shí)绍昂,每個(gè)箱子其實(shí)是一個(gè)鏈表啦粹,屬于同一個(gè)箱子的所有鍵值對(duì)都會(huì)排列在鏈表中。

哈希表還有一個(gè)重要的屬性: 負(fù)載因子(load factor)窘游,它用來(lái)衡量哈希表的 空/滿 程度唠椭,一定程度上也可以體現(xiàn)查詢的效率,計(jì)算公式為:

負(fù)載因子 = 總鍵值對(duì)數(shù) / 箱子個(gè)數(shù)

負(fù)載因子越大忍饰,意味著哈希表越滿贪嫂,越容易導(dǎo)致沖突,性能也就越低艾蓝。因此力崇,一般來(lái)說(shuō),當(dāng)負(fù)載因子大于某個(gè)常數(shù)(可能是 1赢织,或 0.75 等)時(shí)亮靴,哈希表將自動(dòng)擴(kuò)容。

哈希表在自動(dòng)擴(kuò)容時(shí)于置,一般會(huì)創(chuàng)建兩倍于原來(lái)個(gè)數(shù)的箱子茧吊,因此即使 key 的哈希值不變,對(duì)箱子個(gè)數(shù)取余的結(jié)果也會(huì)發(fā)生改變饱狂,因此所有鍵值對(duì)的存放位置都有可能發(fā)生改變,這個(gè)過(guò)程也稱為重哈希(rehash)雏婶。

哈希表的擴(kuò)容并不總是能夠有效解決負(fù)載因子過(guò)大的問(wèn)題。假設(shè)所有 key 的哈希值都一樣,那么即使擴(kuò)容以后他們的位置也不會(huì)變化参歹。雖然負(fù)載因子會(huì)降低,但實(shí)際存儲(chǔ)在每個(gè)箱子中的鏈表長(zhǎng)度并不發(fā)生改變郎汪,因此也就不能提高哈希表的查詢性能照筑。

基于以上總結(jié)懦铺,細(xì)心的讀者可能會(huì)發(fā)現(xiàn)哈希表的兩個(gè)問(wèn)題:

①趁窃、如果哈希表中本來(lái)箱子就比較多,擴(kuò)容時(shí)需要重新哈希并移動(dòng)數(shù)據(jù)急前,性能影響較大醒陆。

②、如果哈希函數(shù)設(shè)計(jì)不合理裆针,哈希表在極端情況下會(huì)變成線性表刨摩,性能極低。

我們分別通過(guò) Java 和 Redis 的源碼來(lái)理解以上問(wèn)題世吨,并看看他們的解決方案澡刹。

3. Java8 中的哈希表

JDK 的代碼是開源的,可以從這里下載到另假,我們要找的 HashMap.java 文件的目錄在 openjdk/jdk/src/share/classes/java/util/HashMap.java。

HashMap 是基于 HashTable 的一種數(shù)據(jù)結(jié)構(gòu)怕犁,在普通哈希表的基礎(chǔ)上边篮,它支持多線程操作以及空的 key 和 value。

在 HashMap 中定義了幾個(gè)常量:

  1. DEFAULT_INITIAL_CAPACITY: 初始容量奏甫,也就是默認(rèn)會(huì)創(chuàng)建 16 個(gè)箱子戈轿,箱子的個(gè)數(shù)不能太多或太少。如果太少阵子,很容易觸發(fā)擴(kuò)容思杯;如果太多,遍歷哈希表會(huì)比較慢挠进。
  1. MAXIMUM_CAPACITY: 哈希表最大容量色乾,一般情況下只要內(nèi)存夠用,哈希表不會(huì)出現(xiàn)問(wèn)題领突。
  1. DEFAULT_LOAD_FACTOR: 默認(rèn)的負(fù)載因子暖璧。因此初始情況下,當(dāng)鍵值對(duì)的數(shù)量大于 16 * 0.75 = 12 時(shí)君旦,就會(huì)觸發(fā)擴(kuò)容澎办。
  1. TREEIFY_THRESHOLD: 上文說(shuō)過(guò)嘲碱,如果哈希函數(shù)不合理,即使擴(kuò)容也無(wú)法減少箱子中鏈表的長(zhǎng)度局蚀,因此 Java 的處理方案是當(dāng)鏈表太長(zhǎng)時(shí)麦锯,轉(zhuǎn)換成紅黑樹。這個(gè)值表示當(dāng)某個(gè)箱子中琅绅,鏈表長(zhǎng)度大于 8 時(shí)扶欣,有可能會(huì)轉(zhuǎn)化成樹。
  1. UNTREEIFY_THRESHOLD: 在哈希表擴(kuò)容時(shí)奉件,如果發(fā)現(xiàn)鏈表長(zhǎng)度小于 6宵蛀,則會(huì)由樹重新退化為鏈表。
  1. MIN_TREEIFY_CAPACITY: 在轉(zhuǎn)變成樹之前县貌,還會(huì)有一次判斷术陶,只有鍵值對(duì)數(shù)量大于 64 才會(huì)發(fā)生轉(zhuǎn)換。這是為了避免在哈希表建立初期煤痕,多個(gè)鍵值對(duì)恰好被放入了同一個(gè)鏈表中而導(dǎo)致不必要的轉(zhuǎn)化梧宫。

學(xué)過(guò)概率論的讀者也許知道,理想狀態(tài)下哈希表的每個(gè)箱子中摆碉,元素的數(shù)量遵守泊松分布:

泊松分布

當(dāng)負(fù)載因子為 0.75 時(shí)塘匣,上述公式中 λ 約等于 0.5,因此箱子中元素個(gè)數(shù)和概率的關(guān)系如下:

數(shù)量 概率
0 0.60653066
1 0.30326533
2 0.07581633
3 0.01263606
4 0.00157952
5 0.00015795
6 0.00001316
7 0.00000094
8 0.00000006

這就是為什么箱子中鏈表長(zhǎng)度超過(guò) 8 以后要變成紅黑樹巷帝,因?yàn)樵谡G闆r下出現(xiàn)這種現(xiàn)象的幾率小到忽略不計(jì)忌卤。一旦出現(xiàn),幾乎可以認(rèn)為是哈希函數(shù)設(shè)計(jì)有問(wèn)題導(dǎo)致的楞泼。

Java 對(duì)哈希表的設(shè)計(jì)一定程度上避免了不恰當(dāng)?shù)墓:瘮?shù)導(dǎo)致的性能問(wèn)題驰徊,每一個(gè)箱子中的鏈表可以與紅黑樹切換。

4. Redis

Redis 是一個(gè)高效的 key-value 緩存系統(tǒng)堕阔,也可以理解為基于鍵值對(duì)的數(shù)據(jù)庫(kù)棍厂。它對(duì)哈希表的設(shè)計(jì)有非常多值得學(xué)習(xí)的地方,在不影響源代碼邏輯的前提下我會(huì)盡可能簡(jiǎn)化超陆,突出重點(diǎn)牺弹。

1、數(shù)據(jù)結(jié)構(gòu)

在 Redis 中时呀,字典是一個(gè) dict 類型的結(jié)構(gòu)體张漂,定義在 src/dict.h 中:

typedef struct dict
{
    dictht ht[2];

    long rehashidx; /* rehashing not in progress if rehashidx == -1 */
} dict;

這里的 dictht 是用于存儲(chǔ)數(shù)據(jù)的結(jié)構(gòu)體。注意到我們定義了一個(gè)長(zhǎng)度為 2 的數(shù)組谨娜,它是為了解決擴(kuò)容時(shí)速度較慢而引入的鹃锈,其原理后面會(huì)詳細(xì)介紹,rehashidx 也是在擴(kuò)容時(shí)需要用到瞧预。先看一下 dictht 的定義:

typedef struct dictht
{
    dictEntry **table;
    unsigned long size;
    unsigned long used;
} dictht;

可見結(jié)構(gòu)體中有一個(gè)二維數(shù)組 table屎债,元素類型是 dictEntry仅政,對(duì)應(yīng)著存儲(chǔ)的一個(gè)鍵值對(duì):

typedef struct dictEntry
{
    void *key;
    union {
        void *val;
        uint64_t u64;
        int64_t s64;
        double d;
    } v;
    struct dictEntry *next;
} dictEntry;

從 next 指針以及二維數(shù)組可以看出,Redis 的哈希表采用拉鏈法解決沖突盆驹。

整個(gè)字典的層次結(jié)構(gòu)如下圖所示:

Dict 字典層次結(jié)構(gòu)

2圆丹、添加元素

向字典中添加鍵值對(duì)的底層實(shí)現(xiàn)如下:

dictEntry *dictAddRaw(dict *d, void *key)
{
     int index;
     dictEntry *entry;
     dictht *ht;

     if (dictIsRehashing(d)) _dictRehashStep(d);
     if ((index = _dictKeyIndex(d, key)) == -1)
          return NULL;

     ht = dictIsRehashing(d) ? &d->ht[1] : &d->ht[0];
     entry = zmalloc(sizeof(*entry));
     entry->next = ht->table[index];
     ht->table[index] = entry;
     ht->used++;
     dictSetKey(d, entry, key);

     return entry;
}

dictIsRehashing() 函數(shù)用來(lái)判斷哈希表是否正在重新哈希。所謂的重新哈希是指在擴(kuò)容時(shí)躯喇,原來(lái)的鍵值對(duì)需要改變位置辫封。為了優(yōu)化重哈希的體驗(yàn),Redis 每次只會(huì)移動(dòng)一個(gè)箱子中的內(nèi)容廉丽,下一節(jié)會(huì)做詳細(xì)解釋倦微。

仔細(xì)閱讀指針操作部分就會(huì)發(fā)現(xiàn),新插入的鍵值對(duì)會(huì)放在箱子中鏈表的頭部正压,而不是在尾部繼續(xù)插入欣福。這個(gè)細(xì)節(jié)上的改動(dòng)至少帶來(lái)兩個(gè)好處:

  1. 找到鏈表尾部的時(shí)間復(fù)雜度是 O(n),或者需要使用額外的內(nèi)存地址來(lái)保存鏈表尾部的位置焦履。頭插法可以節(jié)省插入耗時(shí)拓劝。

  2. 對(duì)于一個(gè)數(shù)據(jù)庫(kù)系統(tǒng)來(lái)說(shuō),最新插入的數(shù)據(jù)往往更有可能頻繁的被獲取嘉裤。頭插法可以節(jié)省查找耗時(shí)郑临。

3、增量式擴(kuò)容

所謂的增量式擴(kuò)容是指屑宠,當(dāng)需要重哈希時(shí)厢洞,每次只遷移一個(gè)箱子里的鏈表,這樣擴(kuò)容時(shí)不會(huì)出現(xiàn)性能的大幅度下降典奉。

為了標(biāo)記哈希表正處于擴(kuò)容階段躺翻,我們?cè)?dict 結(jié)構(gòu)體中使用 rehashidx 來(lái)表示當(dāng)前正在遷移哪個(gè)箱子里的數(shù)據(jù)。由于在結(jié)構(gòu)體中實(shí)際上有兩個(gè)哈希表秋柄,如果添加新的鍵值對(duì)時(shí)哈希表正在擴(kuò)容获枝,我們首先從第一個(gè)哈希表中遷移一個(gè)箱子的數(shù)據(jù)到第二個(gè)哈希表中蠢正,然后鍵值對(duì)會(huì)被插入到第二個(gè)哈希表中骇笔。

在上面給出的 dictAddRaw 方法的實(shí)現(xiàn)中,有兩句代碼:

if (dictIsRehashing(d)) _dictRehashStep(d);
// ...
ht = dictIsRehashing(d) ? &d->ht[1] : &d->ht[0];

第二句就是用來(lái)選擇插入到哪個(gè)哈希表中嚣崭,第一句話則是遷移 rehashidx 位置上的鏈表笨触。它實(shí)際上會(huì)調(diào)用 dictRehash(d, 1),也就是說(shuō)是單步長(zhǎng)的遷移雹舀。dictRehash 函數(shù)的實(shí)現(xiàn)如下:

int dictRehash(dict *d, int n) 
{
     int empty_visits = n*10; /* Max number of empty buckets to visit. */

     while(n-- && d->ht[0].used != 0) {
          dictEntry *de, *nextde;

          while(d->ht[0].table[d->rehashidx] == NULL) {
               d->rehashidx++;
               if (--empty_visits == 0) return 1;
          }
          de = d->ht[0].table[d->rehashidx];
         
           /* Move all the keys in this bucket from the old to the new hash HT */
          while(de) {
               unsigned int h;
               nextde = de->next;
               /* Get the index in the new hash table */
               h = dictHashKey(d, de->key) & d->ht[1].sizemask;
               de->next = d->ht[1].table[h];
               d->ht[1].table[h] = de;
               d->ht[0].used—;
               d->ht[1].used++;
               de = nextde;
          }
          d->ht[0].table[d->rehashidx] = NULL;
          d->rehashidx++;
     }

     /* Check if we already rehashed the whole table... */
     if (d->ht[0].used == 0) {
          zfree(d->ht[0].table);
          d->ht[0] = d->ht[1];
          _dictReset(&d->ht[1]);
          d->rehashidx = -1;
          
          return 0;
     }
     return 1;
}

這段代碼比較長(zhǎng)芦劣,但是并不難理解。它由一個(gè) while 循環(huán)和 if 語(yǔ)句組成说榆。在單步遷移的情況下虚吟,最外層的 while 循環(huán)沒(méi)有意義寸认,而它內(nèi)部又可以分為兩個(gè) while 循環(huán)。

第一個(gè)循環(huán)用來(lái)更新 rehashidx 的值串慰,因?yàn)橛行┫渥訛榭掌?rehashidx 并非每次都比原來(lái)前進(jìn)一個(gè)位置,而是有可能前進(jìn)幾個(gè)位置邦鲫,但最多不超過(guò) 10灸叼。第二個(gè)循環(huán)則用來(lái)復(fù)制鏈表數(shù)據(jù)。

最外面的 if 判斷中庆捺,如果發(fā)現(xiàn)舊哈希表已經(jīng)全部完成遷移古今,就會(huì)釋放舊哈希表的內(nèi)存,同時(shí)把新的哈希表賦值給舊的哈希表滔以,最后把 rehashidx 重新設(shè)置為 -1捉腥,表示重哈希過(guò)程結(jié)束。

4醉者、默認(rèn)哈希函數(shù)

與 Java 不同的是但狭,Redis 提供了 void * 類型 key 的哈希函數(shù),也就是通過(guò)任何類型的 key 的指針都可以求出哈希值撬即。具體算法定義在 dictGenHashFunction 函數(shù)中立磁,由于代碼過(guò)長(zhǎng),而且都是一些位運(yùn)算剥槐,就不展示了唱歧。

它的實(shí)現(xiàn)原理是根據(jù)指針地址和這一塊內(nèi)存的長(zhǎng)度,獲取內(nèi)存中的值粒竖,并且放入到一個(gè)數(shù)組當(dāng)中颅崩,可見這個(gè)數(shù)組僅由 0 和 1 構(gòu)成。然后再對(duì)這些數(shù)字做哈希運(yùn)算蕊苗。因此即使兩個(gè)指針指向的地址不同沿后,但只要其中內(nèi)容相同,就可以得到相同的哈希值朽砰。

5. 歸納對(duì)比

首先我們回顧一下 Java 和 Redis 的解決方案尖滚。

Java 的長(zhǎng)處在于當(dāng)哈希函數(shù)不合理導(dǎo)致鏈表過(guò)長(zhǎng)時(shí),會(huì)使用紅黑樹來(lái)保證插入和查找的效率瞧柔。缺點(diǎn)是當(dāng)哈希表比較大時(shí)漆弄,如果擴(kuò)容會(huì)導(dǎo)致瞬時(shí)效率降低。

Redis 通過(guò)增量式擴(kuò)容解決了這個(gè)缺點(diǎn)造锅,同時(shí)拉鏈法的實(shí)現(xiàn)(放在鏈表頭部)值得我們學(xué)習(xí)撼唾。Redis 還提供了一個(gè)經(jīng)過(guò)嚴(yán)格測(cè)試,表現(xiàn)良好的默認(rèn)哈希函數(shù)哥蔚,避免了鏈表過(guò)長(zhǎng)的問(wèn)題倒谷。

Objective-C 的實(shí)現(xiàn)和 Java 比較類似蛛蒙,當(dāng)我們需要重寫 isEqual() 方法時(shí),還需要重寫 hash 方法渤愁。這兩種語(yǔ)言并沒(méi)有提供一個(gè)通用的宇驾、默認(rèn)的哈希函數(shù),主要是考慮到 isEqual() 方法可能會(huì)被重寫猴伶,兩個(gè)內(nèi)存數(shù)據(jù)不同的對(duì)象可能在語(yǔ)義上被認(rèn)為是相同的课舍。如果使用默認(rèn)的哈希函數(shù)就會(huì)得到不同的哈希值,這兩個(gè)對(duì)象就會(huì)同時(shí)被添加到 NSSet 集合中他挎,這可能違背我們的期望結(jié)果筝尾。

根據(jù)我的了解,Redis 并不支持重寫哈希方法办桨,難道 Redis 就沒(méi)有考慮到這個(gè)問(wèn)題么筹淫?實(shí)際上還要從 Redis 的定位說(shuō)起。由于它是一個(gè)高效的呢撞,Key-Value 存儲(chǔ)系統(tǒng)损姜,它的 key 并不會(huì)是一個(gè)對(duì)象,而是一個(gè)用來(lái)唯一確定對(duì)象的標(biāo)記殊霞。

一般情況下摧阅,如果要存儲(chǔ)某個(gè)用戶的信息,key 的值可能是這樣: user:100001绷蹲。Redis 只關(guān)心 key 的內(nèi)存中的數(shù)據(jù)棒卷,因此只要是可以用二進(jìn)制表示的內(nèi)容都可以作為 key,比如一張圖片祝钢。Redis 支持的數(shù)據(jù)結(jié)構(gòu)包括哈希表和集合(Set)比规,但是其中的數(shù)據(jù)類型只能是字符串。因此 Redis 并不存在對(duì)象等同性的考慮拦英,也就可以提供默認(rèn)的哈希函數(shù)了蜒什。

Redis、Java疤估、Objective-C 之間的異同再次證明了一點(diǎn):

沒(méi)有完美的架構(gòu)灾常,只有滿足需求的架構(gòu)。

6. 總結(jié)

回到文章開頭的問(wèn)題中來(lái)做裙,有兩個(gè)字典岗憋,分別存有 100 條數(shù)據(jù)和 10000 條數(shù)據(jù)肃晚,如果用一個(gè)不存在的 key 去查找數(shù)據(jù)锚贱,在哪個(gè)字典中速度更快?

完整的答案是:

在 Redis 中关串,得益于自動(dòng)擴(kuò)容和默認(rèn)哈希函數(shù)拧廊,兩者查找速度一樣快监徘。

在 Java 和 Objective-C 中,如果哈希函數(shù)不合理吧碾,返回值過(guò)于集中凰盔,會(huì)導(dǎo)致大字典更慢。Java 由于存在鏈表和紅黑樹互換機(jī)制倦春,搜索時(shí)間呈對(duì)數(shù)級(jí)增長(zhǎng)户敬,而非線性增長(zhǎng)。在理想的哈希函數(shù)下睁本,無(wú)論字典多大尿庐,搜索速度都是一樣快。

最后呢堰,整理了一下本文提到的知識(shí)點(diǎn)抄瑟,希望大家讀完文章后對(duì)以下問(wèn)題有比較清楚透徹的理解:

  1. 哈希表中負(fù)載因子的概念
  2. 哈希表擴(kuò)容的過(guò)程,以及對(duì)查找性能的影響
  3. 哈希表擴(kuò)容速度的優(yōu)化枉疼,拉鏈法插入新元素的優(yōu)化皮假,鏈表過(guò)長(zhǎng)時(shí)的優(yōu)化
  4. 不同語(yǔ)言、使用場(chǎng)景下的取舍

7. 學(xué)習(xí)文章

? OpenJDK Source Release
? HashMap vs Hashtable vs HashSet
? 泊松分布
? Redis Source code
? An introduction to Redis data types and abstractions
? 韋易笑 # 基礎(chǔ)優(yōu)化-讓哈希表更公平一些

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末骂维,一起剝皮案震驚了整個(gè)濱河市惹资,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌航闺,老刑警劉巖布轿,帶你破解...
    沈念sama閱讀 218,607評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異来颤,居然都是意外死亡汰扭,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,239評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門福铅,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)萝毛,“玉大人,你說(shuō)我怎么就攤上這事滑黔“拾” “怎么了?”我有些...
    開封第一講書人閱讀 164,960評(píng)論 0 355
  • 文/不壞的土叔 我叫張陵略荡,是天一觀的道長(zhǎng)庵佣。 經(jīng)常有香客問(wèn)我,道長(zhǎng)汛兜,這世上最難降的妖魔是什么巴粪? 我笑而不...
    開封第一講書人閱讀 58,750評(píng)論 1 294
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上肛根,老公的妹妹穿的比我還像新娘辫塌。我一直安慰自己,他們只是感情好派哲,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,764評(píng)論 6 392
  • 文/花漫 我一把揭開白布臼氨。 她就那樣靜靜地躺著,像睡著了一般芭届。 火紅的嫁衣襯著肌膚如雪储矩。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,604評(píng)論 1 305
  • 那天褂乍,我揣著相機(jī)與錄音椰苟,去河邊找鬼。 笑死树叽,一個(gè)胖子當(dāng)著我的面吹牛舆蝴,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播题诵,決...
    沈念sama閱讀 40,347評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼洁仗,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了性锭?” 一聲冷哼從身側(cè)響起赠潦,我...
    開封第一講書人閱讀 39,253評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎草冈,沒(méi)想到半個(gè)月后她奥,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,702評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡怎棱,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,893評(píng)論 3 336
  • 正文 我和宋清朗相戀三年哩俭,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片拳恋。...
    茶點(diǎn)故事閱讀 40,015評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡凡资,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出谬运,到底是詐尸還是另有隱情隙赁,我是刑警寧澤,帶...
    沈念sama閱讀 35,734評(píng)論 5 346
  • 正文 年R本政府宣布梆暖,位于F島的核電站苍在,受9級(jí)特大地震影響响蕴,放射性物質(zhì)發(fā)生泄漏绩蜻。R本人自食惡果不足惜礁苗,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,352評(píng)論 3 330
  • 文/蒙蒙 一弟灼、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧蝗肪,春花似錦、人聲如沸蠕趁。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,934評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)俺陋。三九已至豁延,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間腊状,已是汗流浹背诱咏。 一陣腳步聲響...
    開封第一講書人閱讀 33,052評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留缴挖,地道東北人袋狞。 一個(gè)月前我還...
    沈念sama閱讀 48,216評(píng)論 3 371
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像映屋,于是被迫代替她去往敵國(guó)和親苟鸯。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,969評(píng)論 2 355

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