數(shù)據(jù)結(jié)構(gòu)思維 第十四章 持久化

第十四章 持久化

原文:Chapter 14 Persistence

譯者:飛龍

協(xié)議:CC BY-NC-SA 4.0

自豪地采用谷歌翻譯

在接下來的幾個(gè)練習(xí)中梁剔,我們將返回到網(wǎng)頁搜索引擎的構(gòu)建昼钻。為了回顧,搜索引擎的組件是:

  • 抓让镒摇:我們需要一個(gè)程序末融,可以下載一個(gè)網(wǎng)頁尝胆,解析它,并提取文本和任何其他頁面的鏈接捺球。
  • 索引:我們需要一個(gè)索引缸浦,可以查找檢索項(xiàng)并找到包含它的頁面。
  • 檢索:我們需要一種方法氮兵,從索引中收集結(jié)果裂逐,并識別與檢索項(xiàng)最相關(guān)的頁面。

如果你做了練習(xí) 8.3泣栈,你使用 Java 映射實(shí)現(xiàn)了一個(gè)索引卜高。在本練習(xí)中,我們將重新審視索引器掺涛,并創(chuàng)建一個(gè)新版本,將結(jié)果存儲(chǔ)在數(shù)據(jù)庫中疼进。

如果你做了練習(xí) 7.4拣帽,你創(chuàng)建了一個(gè)爬蟲修陡,它跟蹤它找到的第一個(gè)鏈接。在下一個(gè)練習(xí)中,我們將制作一個(gè)更通用的版本砾跃,將其查找到的每個(gè)鏈接存儲(chǔ)在隊(duì)列中,并對其進(jìn)行排序莹桅。

然后,最后沮峡,你將處理檢索問題疚脐。

在這些練習(xí)中,我提供較少的起始代碼邢疙,你將做出更多的設(shè)計(jì)決策棍弄。這些練習(xí)也更加開放。我會(huì)提出一些最低限度的目標(biāo)疟游,你應(yīng)該嘗試實(shí)現(xiàn)它們呼畸,但如果你想挑戰(zhàn)自己,有很多方法可以讓你更深入颁虐。

現(xiàn)在蛮原,讓我們開始編寫一個(gè)新版本的索引器。

14.1 Redis

索引器的之前版本聪廉,將索引存儲(chǔ)在兩個(gè)數(shù)據(jù)結(jié)構(gòu)中:TermCounter將檢索詞映射為網(wǎng)頁上顯示的次數(shù)瞬痘,以及Index將檢索詞映射為出現(xiàn)的頁面集合。

這些數(shù)據(jù)結(jié)構(gòu)存儲(chǔ)在正在運(yùn)行的 Java 程序的內(nèi)存中板熊,這意味著當(dāng)程序停止運(yùn)行時(shí)框全,索引會(huì)丟失。僅在運(yùn)行程序的內(nèi)存中存儲(chǔ)的數(shù)據(jù)稱為“易失的”干签,因?yàn)槌绦蚪Y(jié)束時(shí)會(huì)消失津辩。

在創(chuàng)建它的程序結(jié)束后,仍然存在的數(shù)據(jù)稱為“持久的”容劳。通常喘沿,存儲(chǔ)在文件系統(tǒng)中的文件,以及存儲(chǔ)在數(shù)據(jù)庫中的數(shù)據(jù)是持久的竭贩。

使數(shù)據(jù)持久化的一種簡單方法是蚜印,將其存儲(chǔ)在文件中。在程序結(jié)束之前留量,它可以將其數(shù)據(jù)結(jié)構(gòu)轉(zhuǎn)換為 JSON 格式(http://thinkdast.com/json)窄赋,然后將它們寫入文件哟冬。當(dāng)它再次啟動(dòng)時(shí),它可以讀取文件并重建數(shù)據(jù)結(jié)構(gòu)忆绰。

但這個(gè)解決方案有幾個(gè)問題:

  • 讀取和寫入大型數(shù)據(jù)結(jié)構(gòu)(如 Web 索引)會(huì)很慢浩峡。
  • 整個(gè)數(shù)據(jù)結(jié)構(gòu)可能不適合單個(gè)運(yùn)行程序的內(nèi)存。
  • 如果程序意外結(jié)束(例如错敢,由于斷電)翰灾,則自程序上次啟動(dòng)以來所做的任何更改都將丟失。

一個(gè)更好的選擇是提供持久存儲(chǔ)的數(shù)據(jù)庫稚茅,并且能夠讀取和寫入數(shù)據(jù)庫的部分纸淮,而無需讀取和寫入整個(gè)數(shù)據(jù)。

有多種數(shù)據(jù)庫管理系統(tǒng)(DBMS)提供不同的功能亚享。你可以在 http://thinkdast.com/database 閱讀概述萎馅。

我為這個(gè)練習(xí)推薦的數(shù)據(jù)庫是 Redis,它提供了類似于 Java 數(shù)據(jù)結(jié)構(gòu)的持久數(shù)據(jù)結(jié)構(gòu)虹蒋。具體來說,它提供:

字符串列表飒货,與 Java 的List類似魄衅。
哈希,類似于 Java 的Map塘辅。
字符串集合晃虫,類似于 Java 的Set

譯者注:另外還有類似于 Java 的LinkedHashSet的有序集合扣墩。

Redis 是一個(gè)“鍵值數(shù)據(jù)庫”哲银,這意味著它包含的數(shù)據(jù)結(jié)構(gòu)(值)由唯一的字符串(鍵)標(biāo)識。Redis 中的鍵與 Java 中的引用相同:它標(biāo)識一個(gè)對象呻惕。我們稍后會(huì)看到一些例子荆责。

14.2 Redis 客戶端和服務(wù)端

Redis 通常運(yùn)行為遠(yuǎn)程服務(wù);其實(shí)它的名字代表“REmote DIctionary Server”(遠(yuǎn)程字典服務(wù)亚脆,字典其實(shí)就是映射)做院。為了使用 Redis,你必須在某處運(yùn)行 Redis 服務(wù)器濒持,然后使用 Redis 客戶端連接到 Redis 服務(wù)器键耕。有很多方法可用于設(shè)置服務(wù)器,也有許多你可以使用的客戶端柑营。對于這個(gè)練習(xí)屈雄,我建議:

不要自己安裝和運(yùn)行服務(wù)器,請考慮使用像 RedisToGo(http://thinkdast.com/redistogo)這樣的服務(wù)官套,它在云主機(jī)運(yùn)行 Redis酒奶。他們提供了一個(gè)免費(fèi)的計(jì)劃(配置)蚁孔,有足夠的資源用于練習(xí)。
對于客戶端讥蟆,我推薦 Jedis勒虾,它是一個(gè) Java 庫,提供了使用 Redis 的類和方法瘸彤。

以下是更詳細(xì)的說明修然,以幫助你開始使用:

  • 在 RedisToGo 上創(chuàng)建一個(gè)帳號,網(wǎng)址為 http://thinkdast.com/redissign 质况,并選擇所需的計(jì)劃(可能是免費(fèi)的起始計(jì)劃)愕宋。
  • 創(chuàng)建一個(gè)“實(shí)例”,它是運(yùn)行 Redis 服務(wù)器的虛擬機(jī)结榄。如果你單擊“實(shí)例”選項(xiàng)卡中贝,你將看到你的新實(shí)例,由主機(jī)名和端口號標(biāo)識臼朗。例如邻寿,我有一個(gè)名為dory-10534的實(shí)例。
  • 單擊實(shí)例名稱來訪問配置頁面视哑。記下頁面頂部附近的網(wǎng)址绣否,如下所示:
    redis://redistogo:1234567feedfacebeefa1e1234567@dory.redistogo.com:10534
    

這個(gè) URL 包含服務(wù)器的主機(jī)名稱dory.redistogo.com,端口號10534和連接到服務(wù)器所需的密碼挡毅,它是中間較長的字母數(shù)字的字符串蒜撮。你將需要此信息進(jìn)行下一步。

14.3 制作基于 Redis 的索引

在本書的倉庫中跪呈,你將找到此練習(xí)的源文件:

  • JedisMaker.java包含連接到 Redis 服務(wù)器并運(yùn)行幾個(gè) Jedis 方法的示例代碼段磨。
  • JedisIndex.java包含此練習(xí)的起始代碼。
  • JedisIndexTest.java包含JedisIndex的測試代碼耗绿。
  • WikiFetcher.java包含我們在以前的練習(xí)中看到的代碼苹支,用于閱讀網(wǎng)頁并使用jsoup進(jìn)行解析。

你還將需要這些文件误阻,你在以前的練習(xí)中碰到過:

Index.java使用 Java 數(shù)據(jù)結(jié)構(gòu)實(shí)現(xiàn)索引沐序。
TermCounter.java表示從檢索項(xiàng)到其頻率的映射。
WikiNodeIterable.java迭代jsoup生成的 DOM 樹中的節(jié)點(diǎn)堕绩。

如果你有這些文件的有效版本策幼,你可以使用它們進(jìn)行此練習(xí)。如果你沒有進(jìn)行以前的練習(xí)奴紧,或者你對你的解決方案毫無信心特姐,則可以從solutions文件夾復(fù)制我的解決方案。

第一步是使用 Jedis 連接到你的 Redis 服務(wù)器黍氮。JedisMaker.java展示了如何實(shí)現(xiàn)唐含。它從文件讀取你的 Redis 服務(wù)器的信息浅浮,連接到它并使用你的密碼登錄,然后返回一個(gè)可用于執(zhí)行 Redis 操作的 Jedis 對象捷枯。

如果你打開JedisMaker.java滚秩,你應(yīng)該看到JedisMaker類,它是一個(gè)幫助類淮捆,它提供靜態(tài)方法make郁油,它創(chuàng)建一個(gè) Jedis 對象。一旦該對象認(rèn)證完畢攀痊,你可以使用它來與你的 Redis 數(shù)據(jù)庫進(jìn)行通信桐腌。

JedisMaker從名為redis_url.txt的文件讀取你的 Redis 服務(wù)器信息,你應(yīng)該放在目錄src/resources中:

  • 使用文本編輯器創(chuàng)建并編輯ThinkDataStructures/code/src/resources/redis_url.txt苟径。
  • 粘貼服務(wù)器的 URL案站。如果你使用的是 RedisToGo,則 URL 將如下所示:
    redis://redistogo:1234567feedfacebeefa1e1234567@dory.redistogo.com:10534
    

因?yàn)榇宋募愕?Redis 服務(wù)器的密碼棘街,你不應(yīng)將此文件放在公共倉庫中蟆盐。為了幫助你避免意外避免這種情況,倉庫包含.gitignore文件遭殉,使文件難以(但不是不可能)放入你的倉庫舱禽。

現(xiàn)在運(yùn)行ant build來編譯源文件,以及ant JedisMaker來運(yùn)行main中的示例代碼:

    public static void main(String[] args) {

        Jedis jedis = make();
        
        // String
        jedis.set("mykey", "myvalue");
        String value = jedis.get("mykey");
        System.out.println("Got value: " + value);
        
        // Set
        jedis.sadd("myset", "element1", "element2", "element3");
        System.out.println("element2 is member: " + 
                           jedis.sismember("myset", "element2"));
        
        // List
        jedis.rpush("mylist", "element1", "element2", "element3");
        System.out.println("element at index 1: " + 
                           jedis.lindex("mylist", 1));
        
        // Hash
        jedis.hset("myhash", "word1", Integer.toString(2));
        jedis.hincrBy("myhash", "word2", 1);
        System.out.println("frequency of word1: " + 
                           jedis.hget("myhash", "word1"));
        System.out.println("frequency of word1: " + 
                            jedis.hget("myhash", "word2"));
        
        jedis.close();
    }

這個(gè)示例展示了數(shù)據(jù)類型和方法恩沽,你在這個(gè)練習(xí)中最可能使用它們。當(dāng)你運(yùn)行它時(shí)翔始,輸出應(yīng)該是:

Got value: myvalue
element2 is member: true
element at index 1: element2
frequency of word1: 2
frequency of word2: 1

下一節(jié)中我會(huì)解釋代碼的工作原理罗心。

14.4 Redis 數(shù)據(jù)類型

Redis 基本上是一個(gè)從鍵到值的映射,鍵是字符串城瞎,值可以是字符串渤闷,也可以是幾種數(shù)據(jù)類型之一。最基本的 Redis 數(shù)據(jù)類型是字符串脖镀。我將用斜體書寫 Redis 類型飒箭,來區(qū)別于 Java 類型。

為了向數(shù)據(jù)庫添加一個(gè)字符串蜒灰,請使用jedis.set弦蹂,類似于Map.put; 參數(shù)是新的鍵和相應(yīng)的值。為了查找一個(gè)鍵并獲取其值强窖,請使用jedis.get

        jedis.set("mykey", "myvalue");
        String value = jedis.get("mykey");

在這個(gè)例子中凸椿,鍵是"mykey",值是"myvalue"翅溺。

Redis 提供了一個(gè)集合結(jié)構(gòu)脑漫,類似于 Java 的Set<String>髓抑。為了向 Redis 集合添加元素,你可以選擇一個(gè)鍵來標(biāo)識集合优幸,然后使用jedis.sadd

        jedis.sadd("myset", "element1", "element2", "element3");
        boolean flag = jedis.sismember("myset", "element2");

你不必用單獨(dú)的步驟來創(chuàng)建集合吨拍。如果不存在,Redis 會(huì)創(chuàng)建它网杆。在這種情況下羹饰,它會(huì)創(chuàng)建一個(gè)名為myset的集合,包含三個(gè)元素跛璧。

jedis.sismember方法檢查元素是否在一個(gè)集合中严里。添加元素和檢查成員是常數(shù)時(shí)間的操作。

Redis 還提供了一個(gè)列表結(jié)構(gòu)追城,類似于 Java 的List<String>刹碾。jedis.rpush方法在末尾(右端)向列表添加元素:

        jedis.rpush("mylist", "element1", "element2", "element3");
        String element = jedis.lindex("mylist", 1);

同樣,你不必在開始添加元素之前創(chuàng)建結(jié)構(gòu)座柱。此示例創(chuàng)建了一個(gè)名為mylist的列表迷帜,其中包含三個(gè)元素。

jedis.lindex方法使用整數(shù)索引色洞,并返回列表中指定的元素戏锹。添加和訪問元素是常數(shù)時(shí)間的操作。

最后火诸,Redis 提供了一個(gè)哈希結(jié)構(gòu)锦针,類似于 Java 的Map<String, String>jedis.hset方法為哈希表添加新條目:

        jedis.hset("myhash", "word1", Integer.toString(2));
        String value = jedis.hget("myhash", "word1");

此示例創(chuàng)建一個(gè)名為的myhash哈希表置蜀,其中包含一個(gè)條目奈搜,該條目從將鍵word1映射到值"2"

鍵和值都是字符串盯荤,所以如果我們要存儲(chǔ)Integer馋吗,在我們調(diào)用hset之前,我們必須將它轉(zhuǎn)換為String秋秤。當(dāng)我們使用hget查找值時(shí)宏粤,結(jié)果是String,所以我們可能必須將其轉(zhuǎn)換回Integer灼卢。

使用 Redis 的哈希表可能會(huì)令人困惑绍哎,因?yàn)槲覀兪褂靡粋€(gè)鍵來標(biāo)識我們想要的哈希表,然后用另一個(gè)鍵標(biāo)識哈希表中的值鞋真。在 Redis 的上下文中蛇摸,第二個(gè)鍵被稱為“字段”,這可能有助于保持清晰灿巧。所以類似myhash的“鍵”標(biāo)志一個(gè)特定的哈希表赶袄,然后類似word1的“字段”標(biāo)識一個(gè)哈希表中的值揽涮。

對于許多應(yīng)用程序,Redis 哈希表中的值是整數(shù)饿肺,所以 Redis 提供了一些特殊的方法蒋困,比如hincrby將值作為數(shù)字來處理:

        jedis.hincrBy("myhash", "word2", 1);

這個(gè)方法訪問myhash,獲取word2的當(dāng)前值(如果不存在則為0)敬辣,將其遞增1雪标,并將結(jié)果寫回哈希表。

在哈希表中溉跃,設(shè)置村刨,獲取和遞增條目是常數(shù)時(shí)間的操作。

你可以在 http://thinkdast.com/redistypes 上閱讀 Redis 數(shù)據(jù)類型的更多信息撰茎。

14.5 練習(xí) 11

這個(gè)時(shí)候嵌牺,你可以獲取一些信息,你需要使用它們來創(chuàng)建搜索引擎的索引龄糊,它將結(jié)果儲(chǔ)存在 Redis 數(shù)據(jù)庫中逆粹。

現(xiàn)在運(yùn)行ant JedisIndexTest。它應(yīng)該失敗炫惩,因?yàn)槟阌幸恍┕ぷ饕觯?/p>

JedisIndexTest測試了這些方法:

  • JedisIndex僻弹,這是構(gòu)造器,它接受Jedis對象作為參數(shù)他嚷。
  • indexPage蹋绽,它將一個(gè)網(wǎng)頁添加到索引中;它需要一個(gè)StringURL和一個(gè)jsoup Elements對象筋蓖,該對象包含應(yīng)該建立索引的頁面元素卸耘。
  • getCounts,它接收檢索詞扭勉,并返回Map<String, Integer>,包含檢索詞到它在頁面上的出現(xiàn)次數(shù)的映射苛聘。

以下是如何使用這些方法的示例:

        WikiFetcher wf = new WikiFetcher();
        String url1 = 
            "http://en.wikipedia.org/wiki/Java_(programming_language)";
        Elements paragraphs = wf.readWikipedia(url1);

        Jedis jedis = JedisMaker.make();
        JedisIndex index = new JedisIndex(jedis);
        index.indexPage(url1, paragraphs);
        Map<String, Integer> map = index.getCounts("the");

如果我們在結(jié)果map中查看url1涂炎,我們應(yīng)該得到339,這是 Java 維基百科頁面(即我們保存的版本)中设哗,the出現(xiàn)的次數(shù)唱捣。

如果我們再次索引相同的頁面,新的結(jié)果將替換舊的結(jié)果网梢。

將數(shù)據(jù)結(jié)構(gòu)從 Java 翻譯成 Redis 的一個(gè)建議是:記住 Redis 數(shù)據(jù)庫中的每個(gè)對象都以唯一的鍵標(biāo)識震缭,它是一個(gè)字符串。如果同一數(shù)據(jù)庫中有兩種對象战虏,則可能需要向鍵添加前綴來區(qū)分它們拣宰。例如党涕,在我們的解決方案中,我們有兩種對象:

  • 我們將URLSet定義為 Redis 集合巡社,它包含URL膛堤,URL又包含給定檢索詞。每個(gè)URLSet的鍵的起始是"URLSet:"晌该,所以要獲取包含單詞the的 URL肥荔,我們使用鍵"URLSet:the"來訪問該集合。
  • 我們將TermCounter定義為 Redis 哈希表朝群,將出現(xiàn)在頁面上的每個(gè)檢索詞映射到它的出現(xiàn)次數(shù)燕耿。TermCounter每個(gè)鍵的開頭都以"TermCounter:"開頭,以我們正在查找的頁面的 URL 結(jié)尾姜胖。

在我的實(shí)現(xiàn)中誉帅,每個(gè)術(shù)語都有一個(gè)URLSet,每個(gè)索引頁面都有一個(gè)TermCounter谭期。我提供兩個(gè)輔助方法堵第,urlSetKeytermCounterKey來組裝這些鍵。

14.6 更多建議(如果你需要的話)

到了這里隧出,你擁有了完成練習(xí)所需的所有信息踏志,所以如果準(zhǔn)備好了就可以開始了。但是我有幾個(gè)建議胀瞪,你可能想先閱讀它:

  • 對于這個(gè)練習(xí)针余,我提供的指導(dǎo)比以前的練習(xí)少。你必須做出一些設(shè)計(jì)決策凄诞;特別是圆雁,你將必須弄清楚如何將問題分解成,你可以一次性測試的部分帆谍,然后將這些部分組合成一個(gè)完整的解決方案伪朽。如果你嘗試一次寫出整個(gè)項(xiàng)目,而不測試較小的部分汛蝙,調(diào)試可能需要很長時(shí)間烈涮。
  • 使用持久性數(shù)據(jù)的挑戰(zhàn)之一是它是持久的。存儲(chǔ)在數(shù)據(jù)庫中的結(jié)構(gòu)可能會(huì)在每次運(yùn)行程序時(shí)發(fā)生更改窖剑。如果你弄亂了數(shù)據(jù)庫坚洽,你將不得不修復(fù)它或重新開始,然后才能繼續(xù)西土。為了幫助你控制住自己讶舰,我提供的方法叫deleteURLSetsdeleteTermCountersdeleteAllKeys,你可以用它來清理數(shù)據(jù)庫跳昼,并重新開始般甲。你也可以使用printIndex來打印索引的內(nèi)容。
  • 每次調(diào)用 Jedis 的方法時(shí)庐舟,你的客戶端會(huì)向服務(wù)器發(fā)送一條消息欣除,然后服務(wù)器執(zhí)行你請求的操作并發(fā)回消息。如果執(zhí)行許多小操作挪略,可能需要很長時(shí)間历帚。你可以通過將一系列操作分組為一個(gè)Transaction,來提高性能杠娱。

例如挽牢,這是一個(gè)簡單的deleteAllKeys版本:

    public void deleteAllKeys() {
        Set<String> keys = jedis.keys("*");
        for (String key: keys) {
            jedis.del(key);
        }
    }

每次調(diào)用del時(shí),都需要從客戶端到服務(wù)器的雙向通信摊求。如果索引包含多個(gè)頁面禽拔,則該方法需要很長時(shí)間來執(zhí)行。我們可以使用Transaction對象來加速:

    public void deleteAllKeys() {
        Set<String> keys = jedis.keys("*");
        Transaction t = jedis.multi();
        for (String key: keys) {
            t.del(key);
        }
        t.exec();
    }

jedis.multi返回一個(gè)Transaction對象室叉,它提供Jedis對象的所有方法睹栖。但是當(dāng)你調(diào)用Transaction的方法時(shí),它不會(huì)立即執(zhí)行該操作茧痕,并且不與服務(wù)器通信野来。在你調(diào)用exec之前,它會(huì)保存一批操作踪旷。然后它將所有保存的操作同時(shí)發(fā)送到服務(wù)器曼氛,這通常要快得多。

14.7 幾個(gè)設(shè)計(jì)提示

現(xiàn)在你真的擁有了你需要的所有信息令野;你應(yīng)該開始完成練習(xí)舀患。但是如果你卡住了,或者如果你真的不知道如何開始气破,你可以再來一些提示聊浅。

在運(yùn)行測試代碼之前,不要閱讀以下內(nèi)容现使,嘗試一些基本的 Redis 命令低匙,并在JedisIndex.java中編寫幾個(gè)方法。

好的朴下,如果你真的卡住了努咐,這里有一些你可能想要處理的方法:

    /**
     * 向檢索詞相關(guān)的集合中添加 URL
     */
    public void add(String term, TermCounter tc) {}

    /**
     * 查找檢索詞并返回 URL 集合
     */
    public Set<String> getURLs(String term) {}

    /**
     * 返回檢索詞出現(xiàn)在給定 URL 中的次數(shù)
     */
    public Integer getCount(String url, String term) {}

    /**
     * 將 TermCounter 的內(nèi)容存入 Redis
     */
    public List<Object> pushTermCounterToRedis(TermCounter tc) {}

這些是我在解決方案中使用的方法苦蒿,但它們絕對不是將項(xiàng)目分解的唯一方法殴胧。所以如果他們有幫助,請接受這些建議,但是如果沒有团滥,請忽略它們竿屹。

對于每種方法,請考慮首先編寫測試灸姊。當(dāng)你弄清楚如何測試一個(gè)方法時(shí)拱燃,你經(jīng)常會(huì)了解如何編寫它。

祝你好運(yùn)力惯!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末碗誉,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子父晶,更是在濱河造成了極大的恐慌哮缺,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,194評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件甲喝,死亡現(xiàn)場離奇詭異尝苇,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)埠胖,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,058評論 2 385
  • 文/潘曉璐 我一進(jìn)店門糠溜,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人直撤,你說我怎么就攤上這事非竿。” “怎么了谊惭?”我有些...
    開封第一講書人閱讀 156,780評論 0 346
  • 文/不壞的土叔 我叫張陵汽馋,是天一觀的道長。 經(jīng)常有香客問我圈盔,道長豹芯,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,388評論 1 283
  • 正文 為了忘掉前任驱敲,我火速辦了婚禮铁蹈,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘众眨。我一直安慰自己握牧,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,430評論 5 384
  • 文/花漫 我一把揭開白布娩梨。 她就那樣靜靜地躺著沿腰,像睡著了一般。 火紅的嫁衣襯著肌膚如雪狈定。 梳的紋絲不亂的頭發(fā)上颂龙,一...
    開封第一講書人閱讀 49,764評論 1 290
  • 那天习蓬,我揣著相機(jī)與錄音,去河邊找鬼措嵌。 笑死躲叼,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的企巢。 我是一名探鬼主播枫慷,決...
    沈念sama閱讀 38,907評論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼浪规!你這毒婦竟也來了或听?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,679評論 0 266
  • 序言:老撾萬榮一對情侶失蹤笋婿,失蹤者是張志新(化名)和其女友劉穎神帅,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體萌抵,經(jīng)...
    沈念sama閱讀 44,122評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡找御,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,459評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了绍填。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片霎桅。...
    茶點(diǎn)故事閱讀 38,605評論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖讨永,靈堂內(nèi)的尸體忽然破棺而出滔驶,到底是詐尸還是另有隱情,我是刑警寧澤卿闹,帶...
    沈念sama閱讀 34,270評論 4 329
  • 正文 年R本政府宣布揭糕,位于F島的核電站,受9級特大地震影響锻霎,放射性物質(zhì)發(fā)生泄漏著角。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,867評論 3 312
  • 文/蒙蒙 一旋恼、第九天 我趴在偏房一處隱蔽的房頂上張望吏口。 院中可真熱鬧,春花似錦冰更、人聲如沸产徊。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,734評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽舟铜。三九已至,卻和暖如春奠衔,著一層夾襖步出監(jiān)牢的瞬間谆刨,已是汗流浹背奕谭。 一陣腳步聲響...
    開封第一講書人閱讀 31,961評論 1 265
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留痴荐,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,297評論 2 360
  • 正文 我出身青樓官册,卻偏偏與公主長得像生兆,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子膝宁,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,472評論 2 348

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