Python設(shè)計(jì)模式之享元模式

享元模式

由于對(duì)象創(chuàng)建的開銷,面向?qū)ο蟮南到y(tǒng)可能會(huì)面臨性能問題粘捎。性能問題通常在資源受限的嵌入式系統(tǒng)中出現(xiàn)薇缅,比如智能手機(jī)和平板電腦。大型復(fù)雜系統(tǒng)中也可能會(huì)出現(xiàn)同樣的問題攒磨,因?yàn)橐谄渲袆?chuàng)建大量對(duì)象(也可能是用戶)泳桦,這些對(duì)象需要同時(shí)并存。

這個(gè)問題之所以會(huì)發(fā)生娩缰,是因?yàn)楫?dāng)我們創(chuàng)建一個(gè)新對(duì)象時(shí)灸撰,需要分配額外的內(nèi)存。雖然虛擬內(nèi)存理論上為我們提供了無限制的內(nèi)存空間,但現(xiàn)實(shí)卻并非如此浮毯。如果一個(gè)系統(tǒng)耗盡了所有的物理內(nèi)存完疫,就會(huì)開始將內(nèi)存頁替換到二級(jí)存儲(chǔ)設(shè)備,通常是硬盤驅(qū)動(dòng)器(Hard Disk Drive亲轨,HDD)趋惨。 在多數(shù)情況下,由于內(nèi)存和硬盤之間的性能差異惦蚊,這是不能接受的器虾。固態(tài)硬盤(Solid State Drive, SSD)的性能一般比硬盤更好,但并非人人都使用SSD蹦锋,SSD并不會(huì)很快全面替代硬盤(請(qǐng)參考 網(wǎng)頁[t.cn/RqrjS0E])兆沙。

除內(nèi)存使用之外,計(jì)算性能也是一個(gè)考慮點(diǎn)莉掂。圖形軟件葛圃,包括計(jì)算機(jī)游戲,應(yīng)該能夠極快地 渲染3D信息(例如憎妙,有成千上萬棵樹的森林或滿是士兵的村莊)库正。如果一個(gè)3D地帶的每個(gè)對(duì)象都 是單獨(dú)創(chuàng)建,未使用數(shù)據(jù)共享厘唾,那么性能將是無法接受的(請(qǐng)參考網(wǎng)頁[t.cn/Rqrj9qa])褥符。

作為軟件工程師,我們應(yīng)該編寫更好的軟件來解決軟件問題抚垃,而不是要求客戶購買更多更好的硬件什燕。享元設(shè)計(jì)模式通過為相似對(duì)象引入數(shù)據(jù)共享來最小化內(nèi)存使用屋吨,提升性能(請(qǐng)參考網(wǎng)頁[t.cn/RqrjNF3])徙瓶。一個(gè)享元(Flyweight)就是一個(gè)包含狀態(tài)獨(dú)立的不可變(又稱固有的)數(shù)據(jù)的共享對(duì)象椰拒。依賴狀態(tài)的可變(又稱非固有的)數(shù)據(jù)不應(yīng)是享元的一部分,因?yàn)槊總€(gè)對(duì)象的這種信息都不同罕伯,無法共享曲伊。如果享元需要非固有的數(shù)據(jù),應(yīng)該由客戶端代碼顯式地提供(請(qǐng)參考[GOF95追他,第219頁]和網(wǎng)頁[t.cn/RqrjOX3])熊昌。

用一個(gè)例子可能有助于解釋實(shí)際應(yīng)用場(chǎng)景中如何使用享元模式。假設(shè)我們正在設(shè)計(jì)一個(gè)性能關(guān)鍵的游戲湿酸,例如第一人稱射擊(First-Person Shooter婿屹,F(xiàn)PS)游戲。在FPS游戲中推溃,玩家(士兵) 共享一些狀態(tài)昂利,如外在表現(xiàn)和行為。例如,在《反恐精英》游戲中蜂奸,同一團(tuán)隊(duì)(反恐精英或恐怖分子)的所有士兵看起來都是一樣的(外在表現(xiàn))犁苏。同一個(gè)游戲中,(兩個(gè)團(tuán)隊(duì)的)所有士兵都有一些共同的動(dòng)作扩所,比如围详,跳起、低頭等(行為)祖屏。這意味著我們可以創(chuàng)建一個(gè)享元來包含所有共同的數(shù)據(jù)助赞。當(dāng)然,士兵也有許多因人而異的可變數(shù)據(jù)袁勺,這些數(shù)據(jù)不是享元的一部分雹食,比如,槍支期丰、健康狀況和地理位置等群叶。

#以下是來自于github的示例

# -*- coding: utf-8 -*-

"""http://codesnipers.com/?q=python-flyweights"""

import weakref


class FlyweightMeta(type):
    def __new__(mcs, name, parents, dct):
        """

        :param name: class name
        :param parents: class parents
        :param dct: dict: includes class attributes, class methods,
        static methods, etc
        :return: new class
        """

        # set up instances pool
        dct['pool'] = weakref.WeakValueDictionary()
        return super(FlyweightMeta, mcs).__new__(mcs, name, parents, dct)

    @staticmethod
    def _serialize_params(cls, *args, **kwargs):
        """Serialize input parameters to a key.
        Simple implementation is just to serialize it as a string

        """
        args_list = map(str, args)
        args_list.extend([str(kwargs), cls.__name__])
        key = ''.join(args_list)
        return key

    def __call__(cls, *args, **kwargs):
        key = FlyweightMeta._serialize_params(cls, *args, **kwargs)
        pool = getattr(cls, 'pool', {})

        instance = pool.get(key)
        if not instance:
            instance = super(FlyweightMeta, cls).__call__(*args, **kwargs)
            pool[key] = instance
        return instance


class Card(object):

    """The object pool. Has builtin reference counting"""
    _CardPool = weakref.WeakValueDictionary()

    """Flyweight implementation. If the object exists in the
    pool just return it (instead of creating a new one)"""
    def __new__(cls, value, suit):
        obj = Card._CardPool.get(value + suit)
        if not obj:
            obj = object.__new__(cls)
            Card._CardPool[value + suit] = obj
            obj.value, obj.suit = value, suit
        return obj

    # def __init__(self, value, suit):
    #     self.value, self.suit = value, suit

    def __repr__(self):
        return "<Card: %s%s>" % (self.value, self.suit)


class Card2(object):
    __metaclass__ = FlyweightMeta

    def __init__(self, *args, **kwargs):
        # print('Init {}: {}'.format(self.__class__, (args, kwargs)))
        pass


if __name__ == '__main__':
    import sys
    if sys.version_info[0] > 2:
        sys.stderr.write("!!! This example is compatible only with Python 2 ATM !!!\n")
        raise SystemExit(0)

    # comment __new__ and uncomment __init__ to see the difference
    c1 = Card('9', 'h')
    c2 = Card('9', 'h')
    print(c1, c2)
    print(c1 == c2, c1 is c2)
    print(id(c1), id(c2))

    c1.temp = None
    c3 = Card('9', 'h')
    print(hasattr(c3, 'temp'))
    c1 = c2 = c3 = None
    c3 = Card('9', 'h')
    print(hasattr(c3, 'temp'))

    # Tests with metaclass
    instances_pool = getattr(Card2, 'pool')
    cm1 = Card2('10', 'h', a=1)
    cm2 = Card2('10', 'h', a=1)
    cm3 = Card2('10', 'h', a=2)

    assert (cm1 == cm2) != cm3
    assert (cm1 is cm2) is not cm3
    assert len(instances_pool) == 2

    del cm1
    assert len(instances_pool) == 2

    del cm2
    assert len(instances_pool) == 1

    del cm3
    assert len(instances_pool) == 0

### OUTPUT ###
# (<Card: 9h>, <Card: 9h>)
# (True, True)
# (31903856, 31903856)
# True
# False
<Card: 9h> <Card: 9h>
True True
4405784136 4405784136
True
False



---------------------------------------------------------------------------

AttributeError                            Traceback (most recent call last)

<ipython-input-6-f0792005f54c> in <module>()
     96
     97     # Tests with metaclass
---> 98     instances_pool = getattr(Card2, 'pool')
     99     cm1 = Card2('10', 'h', a=1)
    100     cm2 = Card2('10', 'h', a=1)


AttributeError: type object 'Card2' has no attribute 'pool'

上面程序的運(yùn)行結(jié)果

root@localhost:~/software# python flyweight.py
(<Card: 9h>, <Card: 9h>)
(True, True)
(140061805283344, 140061805283344)
True
False

"""http://codesnipers.com/?q=python-flyweights"""

import weakref  


class Card(object):
    """The object pool. Has builtin reference counting"""
    _CardPool = weakref.WeakValueDictionary()

    """Flyweight implementation. If the object exists in the
    pool just return it (instead of creating a new one)"""
    def __new__(cls, value, suit):         
        obj = Card._CardPool.get(value + suit, None)         
        if not obj:             
            obj = object.__new__(cls)             
            Card._CardPool[value + suit] = obj             
            obj.value, obj.suit = value, suit          
        return obj

    # def __init__(self, value, suit):         
    #     self.value, self.suit = value, suit      

    def __repr__(self):         
        return "<Card: %s%s>" % (self.value, self.suit)      


if __name__ == '__main__':
    # comment __new__ and uncomment __init__ to see the difference
    c1 = Card('9', 'h')
    c2 = Card('9', 'h')
    print(c1, c2)
    print(c1 == c2)
    print(id(c1), id(c2))


<Card: 9h> <Card: 9h>
True
4405761024 4405761024

現(xiàn)實(shí)生活的例子

享元模式是一個(gè)用于優(yōu)化的設(shè)計(jì)模式。因此钝荡,要找一個(gè)合適的現(xiàn)實(shí)生活的例子不太容易街立。我們可以把享元看作現(xiàn)實(shí)生活中的緩存區(qū)。例如埠通,許多書店都有專用的書架來擺放最新和最流行的出版物赎离。這就是一個(gè)緩存區(qū),你可以先在這些專用書架上看看有沒有正在找的書籍植阴,如果沒找到蟹瘾,則可以讓圖書管理員來幫你圾浅。

軟件的例子

Exaile音樂播放器(請(qǐng)參考網(wǎng)頁[t.cn/RqrjYHQ])使用享元來復(fù)用通過相同URL識(shí)別的對(duì)象 (在這里是指音樂歌曲)掠手。創(chuàng)建一個(gè)與已有對(duì)象的URL相同的新對(duì)象是沒有意義的,所以復(fù)用相同的對(duì)象來節(jié)約資源(請(qǐng)參考網(wǎng)頁[http://t.cn/RqrjQWr])狸捕。

Peppy是一個(gè)用Python語言實(shí)現(xiàn)的類XEmacs編輯器(請(qǐng)參考網(wǎng)頁[t.cn/hbhSda])喷鸽,它使用享元模式存儲(chǔ)major mode狀態(tài)欄的狀態(tài)。這是因?yàn)槌怯脩粜薷木呐模駝t所有狀態(tài)欄共享相同的屬性(請(qǐng)參考網(wǎng)頁[t.cn/Rqrjm9y])做祝。這個(gè)軟件原作者2014年就放棄了。

應(yīng)用案例

享元旨在優(yōu)化性能和內(nèi)存使用鸡岗。所有嵌入式系統(tǒng)(手機(jī)混槐、平板電腦、游戲終端和微控制器等)和性能關(guān)鍵的應(yīng)用(游戲轩性、3D圖形處理和實(shí)時(shí)系統(tǒng)等)都能從其獲益声登。若想要享元模式有效,需要滿足GoF的《設(shè)計(jì)模式》一書羅列的以下幾個(gè)條件。

  • 應(yīng)用需要使用大量的對(duì)象悯嗓。
  • 對(duì)象太多件舵,存儲(chǔ)/渲染它們的代價(jià)太大。一旦移除對(duì)象中的可變狀態(tài)(因?yàn)樵谛枰畷r(shí)脯厨,應(yīng)該由客戶端代碼顯式地傳遞給享元)铅祸,多組不同的對(duì)象可被相對(duì)更少的共享對(duì)象所替代。
  • 對(duì)象ID對(duì)于應(yīng)用不重要合武。對(duì)象共享會(huì)造成ID比較的失敗临梗,所以不能依賴對(duì)象ID(那些在客戶端代碼看來不同的對(duì)象,最終具有相同的ID)眯杏。

實(shí)現(xiàn)

由于之前已提到樹的例子夜焦,那么就來看看如何實(shí)現(xiàn)它。在這個(gè)例子中岂贩,我們將構(gòu)造一小片水果樹的森林茫经,小到能確保在單個(gè)終端頁面中閱讀整個(gè)輸出。然而萎津,無論你構(gòu)造的森林有多大卸伞,內(nèi)存分配都保持相同。下面這個(gè)Enum類型變量描述三種不同種類的水果樹锉屈。

TreeType = Enum('TreeType', 'apple_tree cherry_tree peach_tree')

現(xiàn)實(shí)生活中的例子

在深入代碼之前荤傲,我們稍稍解釋一下memoization與享元模式之間的區(qū)別。memoization是一種優(yōu)化技術(shù)颈渊,使用一個(gè)緩存來避免重復(fù)計(jì)算那些在更早的執(zhí)行步驟中已經(jīng)計(jì)算好的結(jié)果遂黍。memoization并不是只能應(yīng)用于某種特定的編程方式,比如面向?qū)ο缶幊?Object-Oriented Programming俊嗽,OOP)雾家。在Python中,memoization可以應(yīng)用于方法和簡單的函數(shù)绍豁。享元?jiǎng)t是一種特定于面向?qū)ο缶幊虄?yōu)化的設(shè)計(jì)模式芯咧,關(guān)注的是共享對(duì)象數(shù)據(jù)。

在Python中竹揍,享元可以以多種方式實(shí)現(xiàn)敬飒,但我發(fā)現(xiàn)這個(gè)例子中展示的實(shí)現(xiàn)非常簡潔。pool變量是一個(gè)對(duì)象池(換句話說芬位,是我們的緩存)无拗。注意:pool是一個(gè)類屬性(類的所有實(shí)例共享的一個(gè)變量,請(qǐng)參考網(wǎng)頁[t.cn/zHwpgFe])昧碉。使用特殊方法new(這個(gè)方法在init之前被調(diào)用)英染,我們把Tree類變換成一個(gè)元類阴孟,元類支持自引用。這意味著cls引用的是Tree類(請(qǐng)參考[Lott14税迷,第99頁])永丝。當(dāng)客戶端要?jiǎng)?chuàng)建Tree的一個(gè)實(shí)例時(shí),會(huì)以tree_type參數(shù)傳遞樹的種類箭养。樹的種類用于檢查是否創(chuàng)建過相同種類的樹慕嚷。如果是,則返回之前創(chuàng)建的對(duì)象毕泌;否則喝检,將這個(gè)新的樹種添加到池中,并返回相應(yīng)的新對(duì)象撼泛,如下所示挠说。

def __new__(cls, tree_type):
    obj = cls.pool.get(tree_type, None)
    if not obj:
        obj = object.__new__(cls)
        cls.pool[tree_type] = obj
        obj.tree_type = tree_type
    return obj

方法render()用于在屏幕上渲染一棵樹。注意愿题,享元不知道的所有可變(外部的)信息都需要由客戶端代碼顯式地傳遞损俭。在當(dāng)前案例中,每棵樹都用到一個(gè)隨機(jī)的年齡和一個(gè)x, y形式的位置潘酗。為了讓render()更加有用杆兵,有必要確保沒有樹會(huì)被渲染到另一個(gè)棵之上。你可以考慮把這個(gè)作為練習(xí)仔夺。如果你想讓渲染更加有趣琐脏,可以使用一個(gè)圖形工具包,比如Tkinter或Pygame缸兔。

def render(self, age, x, y):
    print('render a tree of type {} and age {} at ({}, {})'.format(self.tree_type, age, x, y))

main()函數(shù)展示了我們可以如何使用享元模式日裙。一棵樹的年齡是1到30年之間的一個(gè)隨機(jī)值。坐標(biāo)使用1到100之間的隨機(jī)值惰蜜。雖然渲染了18棵樹昂拂,但僅分配了3棵樹的內(nèi)存。輸出的最后一行證明當(dāng)使用享元時(shí)蝎抽,我們不能依賴對(duì)象的ID政钟。函數(shù)id()會(huì)返回對(duì)象的內(nèi)存地址路克。Python規(guī)范并沒有要求id()返回對(duì)象的內(nèi)存地址樟结,只是要求id()為每個(gè)對(duì)象返回一個(gè)唯一性ID,不過CPython(Python的官方實(shí)現(xiàn))正好使用對(duì)象的內(nèi)存地址作為對(duì)象唯一性ID精算。在我們的例子中瓢宦,即使兩個(gè)對(duì)象看起來不相同,但是如果它們屬于同一個(gè)享元家族(在這里灰羽,家族由tree_type定義)驮履,那么它們實(shí)際上有相同的ID鱼辙。當(dāng)然,不同ID的比較仍然可用于不同家族的對(duì)象玫镐,但這僅在客戶端知道實(shí)現(xiàn)細(xì)節(jié)的情況下才可行(通常并非如此)倒戏。

def main():
    rnd = random.Random()
    age_min, age_max = 1, 30 # 單位為年
    min_point, max_point = 0, 100
    tree_counter = 0
    for _ in range(10):
        t1 = Tree(TreeType.apple_tree)
        t1.render(rnd.randint(age_min, age_max),
                rnd.randint(min_point, max_point),
                rnd.randint(min_point, max_point))
        tree_counter += 1
    for _ in range(3):
        t2 = Tree(TreeType.cherry_tree)
        t2.render(rnd.randint(age_min, age_max),
                rnd.randint(min_point, max_point),
                rnd.randint(min_point, max_point))
        tree_counter += 1
    for _ in range(5):
        t3 = Tree(TreeType.peach_tree)
        t3.render(rnd.randint(age_min, age_max),
                    rnd.randint(min_point, max_point),
                    rnd.randint(min_point, max_point))
        tree_counter += 1

    print('trees rendered: {}'.format(tree_counter))
    print('trees actually created: {}'.format(len(Tree.pool)))
    t4 = Tree(TreeType.cherry_tree)
    t5 = Tree(TreeType.cherry_tree)
    t6 = Tree(TreeType.apple_tree)
    print('{} == {}? {}'.format(id(t4), id(t5), id(t4) == id(t5)))
    print('{} == {}? {}'.format(id(t5), id(t6), id(t5) == id(t6)))

下面完整的代碼清單(文件flyweight.py)將給出享元模式如何實(shí)現(xiàn)及使用的完整描述。

import random
from enum import Enum
TreeType = Enum('TreeType', 'apple_tree cherry_tree peach_tree')

class Tree:
    pool = dict()
    def __new__(cls, tree_type):
        obj = cls.pool.get(tree_type, None)
        if not obj:
            obj = object.__new__(cls)
            cls.pool[tree_type] = obj
            obj.tree_type = tree_type
        return obj

    def render(self, age, x, y):
        print('render a tree of type {} and age {} at ({}, {})'.format(self.tree_type, age, x, y))


def main():
    rnd = random.Random()
    age_min, age_max = 1, 30 # 單位為年
    min_point, max_point = 0, 100
    tree_counter = 0
    for _ in range(10):
        t1 = Tree(TreeType.apple_tree)
        t1.render(rnd.randint(age_min, age_max),
                rnd.randint(min_point, max_point),
                rnd.randint(min_point, max_point))
        tree_counter += 1
    for _ in range(3):
        t2 = Tree(TreeType.cherry_tree)
        t2.render(rnd.randint(age_min, age_max),
                rnd.randint(min_point, max_point),
                rnd.randint(min_point, max_point))
        tree_counter += 1
    for _ in range(5):
        t3 = Tree(TreeType.peach_tree)
        t3.render(rnd.randint(age_min, age_max),
                    rnd.randint(min_point, max_point),
                    rnd.randint(min_point, max_point))
        tree_counter += 1

    print('trees rendered: {}'.format(tree_counter))
    print('trees actually created: {}'.format(len(Tree.pool)))
    t4 = Tree(TreeType.cherry_tree)
    t5 = Tree(TreeType.cherry_tree)
    t6 = Tree(TreeType.apple_tree)
    print('{} == {}? {}'.format(id(t4), id(t5), id(t4) == id(t5)))
    print('{} == {}? {}'.format(id(t5), id(t6), id(t5) == id(t6)))

main()
render a tree of type TreeType.apple_tree and age 29 at (13, 7)
render a tree of type TreeType.apple_tree and age 21 at (40, 66)
render a tree of type TreeType.apple_tree and age 3 at (53, 52)
render a tree of type TreeType.apple_tree and age 19 at (100, 84)
render a tree of type TreeType.apple_tree and age 11 at (57, 56)
render a tree of type TreeType.apple_tree and age 22 at (20, 37)
render a tree of type TreeType.apple_tree and age 11 at (7, 16)
render a tree of type TreeType.apple_tree and age 3 at (10, 18)
render a tree of type TreeType.apple_tree and age 17 at (85, 75)
render a tree of type TreeType.apple_tree and age 4 at (97, 34)
render a tree of type TreeType.cherry_tree and age 17 at (72, 29)
render a tree of type TreeType.cherry_tree and age 10 at (26, 79)
render a tree of type TreeType.cherry_tree and age 1 at (85, 56)
render a tree of type TreeType.peach_tree and age 6 at (44, 71)
render a tree of type TreeType.peach_tree and age 4 at (42, 50)
render a tree of type TreeType.peach_tree and age 27 at (85, 24)
render a tree of type TreeType.peach_tree and age 27 at (51, 22)
render a tree of type TreeType.peach_tree and age 23 at (37, 55)
trees rendered: 18
trees actually created: 3
4405186288 == 4405186288? True
4405186288 == 4405186456? False

執(zhí)行上面的示例程序會(huì)顯示被渲染對(duì)象的類型恐似、隨機(jī)年齡以及坐標(biāo)杜跷,還有相同/不同家族享元對(duì)象ID的比較結(jié)果。你在執(zhí)行這個(gè)程序時(shí)別指望能看到與下面相同的輸出矫夷,因?yàn)槟挲g和坐標(biāo)是隨機(jī)的葛闷,對(duì)象ID也依賴內(nèi)存映射。

如果你想更多地練習(xí)一下享元模式双藕,可以嘗試實(shí)現(xiàn)本章提到的FPS士兵淑趾。思考一下哪些數(shù)據(jù)應(yīng)該是享元的一部分(不可變的、內(nèi)部的)忧陪,哪些數(shù)據(jù)不應(yīng)該是(可變的扣泊、外部的)。

小結(jié)

本章中嘶摊,我們學(xué)習(xí)了享元模式旷赖。在我們想要優(yōu)化內(nèi)存使用提高應(yīng)用性能之時(shí),可以使用享元更卒。在所有內(nèi)存受限(想一想嵌入式系統(tǒng))或關(guān)注性能的系統(tǒng)(比如圖形軟件和電子游戲)中等孵,這一點(diǎn)相當(dāng)重要□蹇眨基于GTK+的Exaile音樂播放器使用享元來避免對(duì)象復(fù)制俯萌,Peppy文本編輯器則使用享元來共享狀態(tài)欄的屬性。

一般來說上枕,在應(yīng)用需要?jiǎng)?chuàng)建大量的計(jì)算代價(jià)大但共享許多屬性的對(duì)象時(shí)咐熙,可以使用享元。重點(diǎn)在于將不可變(可共享)的屬性與可變的屬性區(qū)分開辨萍。我們實(shí)現(xiàn)了一個(gè)樹渲染器棋恼,支持三種不同的樹家族。通過顯式地向render()方法提供可變的年齡和x锈玉,y屬性爪飘,我們成功地僅創(chuàng)建了3個(gè)不同的對(duì)象,而不是18個(gè)拉背。雖然那看起來似乎沒什么了不起师崎,但是想象一下,如果是2000棵樹而不是18棵樹椅棺,那又會(huì)怎樣呢?

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末犁罩,一起剝皮案震驚了整個(gè)濱河市齐蔽,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌床估,老刑警劉巖含滴,帶你破解...
    沈念sama閱讀 221,635評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異丐巫,居然都是意外死亡蛙吏,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,543評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門鞋吉,熙熙樓的掌柜王于貴愁眉苦臉地迎上來鸦做,“玉大人,你說我怎么就攤上這事谓着∑糜眨” “怎么了?”我有些...
    開封第一講書人閱讀 168,083評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵赊锚,是天一觀的道長治筒。 經(jīng)常有香客問我,道長舷蒲,這世上最難降的妖魔是什么耸袜? 我笑而不...
    開封第一講書人閱讀 59,640評(píng)論 1 296
  • 正文 為了忘掉前任,我火速辦了婚禮牲平,結(jié)果婚禮上堤框,老公的妹妹穿的比我還像新娘。我一直安慰自己纵柿,他們只是感情好蜈抓,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,640評(píng)論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著昂儒,像睡著了一般沟使。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上渊跋,一...
    開封第一講書人閱讀 52,262評(píng)論 1 308
  • 那天腊嗡,我揣著相機(jī)與錄音,去河邊找鬼拾酝。 笑死燕少,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的微宝。 我是一名探鬼主播棺亭,決...
    沈念sama閱讀 40,833評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼虎眨,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼蟋软!你這毒婦竟也來了镶摘?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,736評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤岳守,失蹤者是張志新(化名)和其女友劉穎凄敢,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體湿痢,經(jīng)...
    沈念sama閱讀 46,280評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡涝缝,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,369評(píng)論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了譬重。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片拒逮。...
    茶點(diǎn)故事閱讀 40,503評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖臀规,靈堂內(nèi)的尸體忽然破棺而出滩援,到底是詐尸還是另有隱情,我是刑警寧澤塔嬉,帶...
    沈念sama閱讀 36,185評(píng)論 5 350
  • 正文 年R本政府宣布玩徊,位于F島的核電站,受9級(jí)特大地震影響谨究,放射性物質(zhì)發(fā)生泄漏恩袱。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,870評(píng)論 3 333
  • 文/蒙蒙 一胶哲、第九天 我趴在偏房一處隱蔽的房頂上張望畔塔。 院中可真熱鬧,春花似錦鸯屿、人聲如沸俩檬。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,340評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽棚辽。三九已至,卻和暖如春冰肴,著一層夾襖步出監(jiān)牢的瞬間屈藐,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,460評(píng)論 1 272
  • 我被黑心中介騙來泰國打工熙尉, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留联逻,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,909評(píng)論 3 376
  • 正文 我出身青樓检痰,卻偏偏與公主長得像包归,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子铅歼,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,512評(píng)論 2 359

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