游戲編程設(shè)計(jì)模式 -- 享元模式

Game Programming Patterns -- Flyweight

原文地址:http://gameprogrammingpatterns.com/flyweight.html
原作者:Robert Nystrom

原創(chuàng)翻譯赤嚼,轉(zhuǎn)載請(qǐng)注明出處

迷霧散盡,一片古老宏偉的森林出現(xiàn)在你的面前。無(wú)數(shù)古老的鐵杉林立缚俏,形成了一座綠色的大教堂婚陪。陽(yáng)光穿過(guò)樹葉茎活,仿佛從斑駁的玻璃穹頂灑落下來(lái)仍劈,形成一道道金色朦朧的光束娄猫。從巨大的樹干中間眺眼望去椅您,這片森林濃密得一眼望不到邊際外冀。

這是我們游戲開發(fā)者夢(mèng)想中超凡脫俗的場(chǎng)景設(shè)定,而類似這種的場(chǎng)景經(jīng)常用一個(gè)名字低調(diào)到不能再低調(diào)的模式來(lái)實(shí)現(xiàn):這就是低調(diào)的享元模式(Flyweight)掀泳。

有樹才有森林

我可以用幾句話就形容出一片茂密的森林雪隧,但是要在一個(gè)實(shí)時(shí)運(yùn)行的游戲中實(shí)現(xiàn)它就是另外一回事了。當(dāng)你要把整片由各不相同的樹木形成的森林呈現(xiàn)在屏幕上時(shí)员舵,一個(gè)圖形程序員所想到的是他在每個(gè)60分之一秒(1幀)都得把這成千上萬(wàn)的多邊形塞到GPU中去脑沿。

我們?cè)谟懻摰氖浅汕先f(wàn)棵樹,每棵樹都有著詳細(xì)的包含了上千個(gè)多邊形的幾何結(jié)構(gòu)马僻。即使你有足夠的內(nèi)存去存放這片森林庄拇,但是如果要在屏幕上渲染它的話,這些數(shù)據(jù)還需要從CPU通過(guò)總線傳輸?shù)紾PU中韭邓。

每棵樹都包含了以下這些部分:

  • 用來(lái)規(guī)定樹的主干措近、分支和樹葉的形狀的多邊形網(wǎng)格模型,女淑。
  • 樹皮和樹葉的紋理瞭郑。
  • 這棵樹在樹林中的位置和朝向。
  • 用來(lái)調(diào)整尺寸和色調(diào)的參數(shù)鸭你,以使得每棵樹看起來(lái)都不一樣屈张。

如果用代碼來(lái)概述的話擒权,差不多就像下面這樣:

class Tree
{
private:
  Mesh mesh_;
  Texture bark_;
  Texture leaves_;
  Vector position_;
  double height_;
  double thickness_;
  Color barkTint_;
  Color leafTint_;
};

如此多的數(shù)據(jù)、網(wǎng)格模型和紋理真的是非常龐大阁谆。用這些去構(gòu)成一個(gè)森林的話碳抄,GPU在一幀內(nèi)所需處理的東西就太多了。幸運(yùn)的是笛厦,有一個(gè)備受推崇的小技巧可以解決這個(gè)問(wèn)題纳鼎。

這個(gè)技巧最關(guān)鍵的觀點(diǎn)是,雖然森林里有成千上萬(wàn)棵樹裳凸,但是其實(shí)它們看起來(lái)都差不多贱鄙。它們可能使用了相同的網(wǎng)格模型和紋理贺拣。這意味著這些樹對(duì)象中的大部分屬性在它們的實(shí)例中都是相同的略步。

如果你讓美術(shù)們給森林中的每棵樹都做一個(gè)不同的模型的話,你不是瘋了就是個(gè)億萬(wàn)富翁嫉到。

***注意梦湘,在下方那些小方框中的東西對(duì)每棵樹來(lái)說(shuō)都是完全一樣的瞎颗。 ***

因此我們可以明確地把書對(duì)象分成兩個(gè)部分來(lái)建模。首先捌议,我們?nèi)〕鏊械臉鋵?duì)象共有的屬性并把它們轉(zhuǎn)移到一個(gè)單獨(dú)的類中:

這看起來(lái)很像是類型對(duì)象(Type Object)模式哼拔。 它們都是把一個(gè)對(duì)象的部分屬性委托給另外一個(gè)對(duì)象,然后把這部分屬性給很多實(shí)例共享瓣颅。 然而倦逐,這兩個(gè)模式的意圖卻是不同的。
類型對(duì)象模式是通過(guò)把類型提取到你自己的對(duì)象模型中宫补,以達(dá)到減少你所需要定義類的數(shù)量的目的檬姥。其帶來(lái)的內(nèi)存共享,只是額外的好處粉怕。而享元模式則純粹是關(guān)于效率的考量健民。

class TreeModel
{
private:
  Mesh mesh_;
  Texture bark_;
  Texture leaves_;
};

游戲中只需要一個(gè)這個(gè)類的實(shí)例就可以了,因?yàn)闆]有理由把相同的網(wǎng)格模型和紋理在內(nèi)存中保存上好幾千份贫贝。接下來(lái)秉犹,森林中每棵樹的實(shí)例所要做的僅僅是對(duì)這個(gè)共享的TreeModel實(shí)例進(jìn)行一次引用。而Tree類中所剩下來(lái)的稚晚,就只有那些每個(gè)樹實(shí)例都不同的屬性:

class Tree
{
private:
  TreeModel* model_;
  
  Vector position_;
  double height_;
  double thickness_;
  Color barkTint_;
  Color leafTint_;
};

你可以想象成這樣:

這對(duì)于在內(nèi)存中存儲(chǔ)這些樹是非常有幫助的凤优,但是這對(duì)渲染卻沒什么作用。在樹林出現(xiàn)在屏幕上之前蜈彼,它首先需要從內(nèi)存進(jìn)入GPU。我們需要用一種顯卡可以理解的方式來(lái)表示我們的這種資源共享方式俺驶。

一千個(gè)實(shí)例

為了減少傳輸?shù)紾PU的數(shù)據(jù)數(shù)量幸逆,我們想要把共享部分的數(shù)據(jù)--那個(gè)TreeModel--只發(fā)送一次棍辕。然后,我們把每棵樹不同的數(shù)據(jù)發(fā)送過(guò)去--它們的位置还绘、顏色和尺寸楚昭。最后,我們告訴GPU拍顷,“就用那一個(gè)模型去渲染所有的樹吧抚太。”

幸運(yùn)的是昔案,如今的圖形編程接口和顯卡硬件已經(jīng)支持這種方式了尿贫。雖然具體實(shí)現(xiàn)的細(xì)節(jié)是很繁瑣的,已經(jīng)超出了本書的范疇踏揣,但是Direct3D和OpenGL是都可以做到這種被稱為實(shí)例渲染(instanced rendering)的功能的庆亡。

***這個(gè)API是由顯卡硬件直接實(shí)現(xiàn)的,這意味著享元模式可能是GOF提出的設(shè)計(jì)模式中唯一實(shí)際被硬件支持的捞稿。 ***

在它們的API中又谋,你需要提供兩部分?jǐn)?shù)據(jù)流。第一部分是需要渲染很多次的共同數(shù)據(jù)塊--樹的網(wǎng)格模型和紋理娱局。第二部分是實(shí)例的列表和這些實(shí)例的參數(shù)彰亥,它們被用來(lái)在每次繪制時(shí)對(duì)第一部分的數(shù)據(jù)進(jìn)行調(diào)整。這樣只需要一次繪制調(diào)用(draw call)衰齐,整個(gè)森林就出現(xiàn)了任斋。

享元模式

現(xiàn)在我們已經(jīng)有了一個(gè)實(shí)際的例子了,接下來(lái)我將帶你通覽一下這個(gè)模式娇斩。享元仁卷,就像它的名字喻示的那樣,是在你需要把一些對(duì)象更加輕量化的時(shí)候發(fā)揮作用的犬第,而這些對(duì)象需要輕量化的原因通常是因?yàn)樗鼈兊臄?shù)量實(shí)在是太多了锦积。

通過(guò)實(shí)例渲染技術(shù),這些對(duì)象所占用的內(nèi)存是沒有其花費(fèi)在總線上把每棵不同的樹傳輸?shù)紾PU里的時(shí)間多的歉嗓,不過(guò)其基本原理是一樣的丰介。

在享元模式中,是通過(guò)把對(duì)象的數(shù)據(jù)分成兩類來(lái)解決這個(gè)問(wèn)題的鉴分。第一類數(shù)據(jù)是對(duì)于對(duì)象的每個(gè)實(shí)例來(lái)說(shuō)相同并且可以共享的部分哮幢。GOF把這部分?jǐn)?shù)據(jù)稱作固有屬性,而我更喜歡把它稱作“上下文無(wú)關(guān)”屬性志珍。在我們的例子中橙垢,就是樹的網(wǎng)格模型和紋理。

另一類數(shù)據(jù)是外部屬性伦糯,它對(duì)于每個(gè)實(shí)例來(lái)說(shuō)都是不同的柜某。在我們的例子中嗽元,就是樹的位置、尺寸和顏色這些喂击。就像上面的代碼示例里一樣剂癌,這個(gè)模式通過(guò)在每個(gè)對(duì)象出現(xiàn)的地方共享一份固有屬性的拷貝,來(lái)達(dá)到節(jié)約內(nèi)存的目的翰绊。

看到這里我們會(huì)覺得佩谷,這不過(guò)是基本的資源共享,很難被稱為一種模式监嗜。這種觀點(diǎn)是片面的谐檀,因?yàn)樵谖覀兊睦又校梢郧逦匕研枰蚕淼膶傩詤^(qū)別出來(lái):就是TreeModel類秤茅。

我發(fā)現(xiàn)這個(gè)模式被使用在一些無(wú)法清楚定義共享對(duì)象的情況下時(shí)稚补,會(huì)顯得不那么顯眼(而因此顯得更加巧妙)。在這些情況下框喳,感覺起來(lái)更像是一個(gè)對(duì)象神奇地在同一時(shí)間出現(xiàn)在了多個(gè)地方课幕。下面讓我來(lái)給你們展示另一個(gè)例子吧。

根之所在

這些樹生長(zhǎng)所需要的地面在我們的游戲中同樣需要被展示出來(lái)五垮。地面可以通過(guò)諸如草地乍惊、泥地、山丘放仗、湖泊润绎、河流以及任何你能想象出來(lái)的地形拼接出來(lái)。我們所要做的地面是基于分塊的(tile-based):世界的表面是一個(gè)由小分塊構(gòu)成的巨大網(wǎng)格诞挨。每一個(gè)分塊都用一種地形來(lái)覆蓋莉撇。

每種地形類型都會(huì)有一些影響游戲體驗(yàn)的屬性:

  • 移動(dòng)消耗,決定了玩家在這種地形上移動(dòng)速度的快慢惶傻。
  • 是否是水面的標(biāo)記棍郎,用來(lái)判斷船只是否可以通過(guò)。
  • 紋理银室,用來(lái)渲染地形涂佃。

因?yàn)槲覀冇螒蜷_發(fā)者對(duì)效率的高低都是偏執(zhí)狂,所以我們不會(huì)允許把這些屬性存儲(chǔ)在游戲中的每一個(gè)地形分塊里蜈敢。因此辜荠,一個(gè)通用的解決方案是為地形類型創(chuàng)建一個(gè)枚舉:

畢竟,我們已經(jīng)在之前的那些樹身上獲得過(guò)教訓(xùn)了抓狭。

enum Terrain
{
  TERRAIN_GRASS,
  TERRAIN_HILL,
  TERRAIN_RIVER
  // Other terrains...
};

然后游戲世界為此保存一個(gè)巨大的二維網(wǎng)格:

class World
{
private:
  Terrain tiles_[WIDTH][HEIGHT];
};

這里我用了一個(gè)二維嵌套數(shù)組來(lái)儲(chǔ)存這個(gè)2D網(wǎng)格伯病。這在C/C++中是非常高效的,因?yàn)檫@兩種語(yǔ)言中會(huì)把數(shù)組里的所有元素打包在一起否过。而在Java或者其他有內(nèi)存管理的語(yǔ)言中狱从,這樣做的話得到的將是其行數(shù)組中每一個(gè)元素都是一個(gè)對(duì)列數(shù)組的引用的數(shù)組膨蛮,而這對(duì)于內(nèi)存使用就不大友好了。
不管在哪種語(yǔ)言中季研,真正寫代碼的時(shí)候都是把這些實(shí)現(xiàn)細(xì)節(jié)隱藏在一個(gè)好用的2D網(wǎng)格數(shù)據(jù)結(jié)構(gòu)里要更好一些。我在這里這么寫只是為了讓它看起來(lái)好理解一些誉察。

為了實(shí)際得到每個(gè)分塊的有用數(shù)據(jù)与涡,我們會(huì)像下面這樣做:

int World::getMovementCost(int x, int y)
{
  switch (tiles_[x][y])
  {
    case TERRAIN_GRASS:   return 1;
    case TERRAIN_HILL:        return 3;
    case TERRAIN_RIVER:     return 2;
    // Other terrains...
  }
}

bool World::isWater(int x, int y)
{
  switch (tiles_[x][y])
  {
    case TERRAIN_GRASS:    return false;
    case TERRAIN_HILL:         return false;
    case TERRAIN_RIVER:      return true;
    // Other terrains...
  }
}

你應(yīng)該明白大概的意思了。雖然這樣是可行的持偏,但是我覺得這樣寫很不好看驼卖。我認(rèn)為移動(dòng)消耗和是否是水面應(yīng)該是一個(gè)地形的數(shù)據(jù),但是這里卻嵌入到了代碼里鸿秆。更糟糕的是酌畜,一種地形類型的數(shù)據(jù)卻分布在了一堆不同的方法中。如果把這些屬性封裝在一起的話應(yīng)該是更好的卿叽。畢竟桥胞,這就是對(duì)象被設(shè)計(jì)出來(lái)的原因。

那么我們?nèi)绻幸粋€(gè)地形的類就好了考婴,就像這樣:

class Terrain
{
public:
  Terrain(int movementCost,
              bool isWater,
              Texture texture)
  : movementCost_(movementCost),
    isWater_(isWater),
    texture_(texture)
  {}

  int getMovementCost() const { return movementCost_; }
  bool isWater() const { return isWater_; }
  const Texture& getTexture() const { return texture_; }
  
private:
  int movementCost_;
  bool isWater_;
  Texture texture_;
};

你可能注意到了贩虾,這里所有的方法都是const類型的。這并不是巧合沥阱。因?yàn)橥粋€(gè)對(duì)象是要在很多不同的環(huán)境中使用的缎罢,如果你要修改它的話,那很多地方都會(huì)同時(shí)發(fā)生改變考杉。
這可能不是你想要的效果策精。分享對(duì)象的內(nèi)存占用的優(yōu)化不應(yīng)該影響到應(yīng)用的可見行為(visible behavior)。因此崇棠,享元對(duì)象幾乎都是不可改變的咽袜。

但是我們并不想為游戲世界里的每一個(gè)分塊都保存一個(gè)實(shí)例。如果你有用心觀察上面那個(gè)類的話易茬,你會(huì)注意到?jīng)]有任何關(guān)于這個(gè)分塊的位置信息酬蹋。在享元模式中,所有地形的狀態(tài)都應(yīng)該是固有的抽莱,或者說(shuō)是上下文無(wú)關(guān)的范抓。

因此,我們沒有理由去給每種地形保存一個(gè)以上的實(shí)例食铐。地面上的每個(gè)草地的分塊和其他的沒有什么不同匕垫。這樣就可以把之前那些枚舉或者Terrain對(duì)象的二維數(shù)組替換成一個(gè)指向Terrain對(duì)象指針的二維數(shù)組:

class World
{
private:
  Terrain* tiles_[WIDTH][HEIGHT];
  
  // Other stuff...
};

所有使用相同地形的分塊都會(huì)指向同一個(gè)地形實(shí)例。

因?yàn)檫@些Terrain實(shí)例要在很多地方使用虐呻,所以如果你要給它們動(dòng)態(tài)分配內(nèi)存的話象泵,它們的生命周期管理起來(lái)會(huì)比較復(fù)雜寞秃。所以,我們把它們直接存儲(chǔ)在World類中:

class World
{
public:
  World()
  : grassTerrain_(1, false, GRASS_TEXTURE),
    hillTerrain_(3, false, HILL_TEXTURE),
    riverTerrain_(2, true, RIVER_TEXTURE)
  {}

private:
  Terrain grassTerrain_;
  Terrain hillTerrain_;
  Terrain riverTerrain_;
  
  // Other stuff...
};

接下來(lái)我們就可以用這些類來(lái)繪制地面了:

void World::generateTerrain()
{
  // Fill the ground with grass.
  for (int x = 0; x < WIDTH; x++)
  {
    for (int y = 0; y < HEIGHT; y++)
    {
      // Sprinkle some hills.
      if (random(10) == 0)
      {
        tiles_[x][y] = &hillTerrain_;
      }
      else
      {
        tiles_[x][y] = &grassTerrain_;
      }
    }
  }

  // Lay a river.
  int x = random(WIDTH);
  for (int y = 0; y < HEIGHT; y++) 
  {
    tiles_[x][y] = &riverTerrain_;
  }
}

我承認(rèn)這確實(shí)不是世界上最好的地形生成算法偶惠。

現(xiàn)在我們可以不用再通過(guò)訪問(wèn)World類中的方法去獲取Terrain的屬性了春寿,而是可以直接獲取到Terrain對(duì)象:

const Terrain& World::getTile(int x, int y) const
{
  return *tiles_[x][y];
}

這樣的話,World類就不再和Terrain的細(xì)節(jié)有任何耦合忽孽。如果你想獲取某個(gè)分塊的屬性的話绑改,你可以從它的對(duì)象中獲取到:

int cost = world.getTile(2, 3).getMovementCost();

我們回到了愉快的與真實(shí)對(duì)象互動(dòng)的API操作上,而且這也幾乎沒有任何額外消耗--一個(gè)指針通常是不會(huì)比一個(gè)枚舉類型大的兄一。

性能如何呢厘线?

注意上面我用的是“幾乎”,因?yàn)閷?duì)善于計(jì)算性能的人來(lái)說(shuō)出革,他們想要知道使用指針和枚舉比起來(lái)到底會(huì)消耗多少性能造壮。通過(guò)指針來(lái)引用terrain意味著間接的查詢。如果想要獲取一些terrain的數(shù)據(jù)骂束,諸如movement cost之類的耳璧,你必須首先跟隨grid中的指針去找到terrain對(duì)象,然后才能獲取到這個(gè)movement cost數(shù)值栖雾。像這樣跟蹤指針會(huì)導(dǎo)致高速緩存缺失(cache miss)楞抡,而這是會(huì)導(dǎo)致性能變差的。

更多有關(guān)指針追蹤和高速緩存缺失的細(xì)節(jié)析藕,請(qǐng)參見章節(jié) 數(shù)據(jù)本地化(Data Locality)召廷。

通常來(lái)說(shuō),優(yōu)化的黃金法則是“profile first”≌穗剩現(xiàn)代計(jì)算機(jī)硬件的復(fù)雜程度已經(jīng)達(dá)到不會(huì)因?yàn)槟硞€(gè)單純的原因而造成性能上的問(wèn)題竞慢。在我對(duì)本章內(nèi)容的測(cè)試中,是沒有發(fā)現(xiàn)使用享元來(lái)代替枚舉有什么影響性能的地方治泥。享元對(duì)速度有非常顯著的提高筹煮。不過(guò)這完全依賴于內(nèi)存上的其他內(nèi)容是如何分布的。

我所確信的時(shí)居夹,使用享元對(duì)象不會(huì)脫離我們的控制败潦。它給你帶來(lái)了面向?qū)ο笮问降暮锰幎]有一堆對(duì)象的額外消耗。如果你發(fā)現(xiàn)自己正在創(chuàng)建一個(gè)枚舉類型准脂,并且正在對(duì)它使用switch方法劫扒,你就可以考慮使用享元來(lái)代替它了。如果你擔(dān)心性能的話狸膏,至少在把你的代碼變成難以維護(hù)的類型之前沟饥,進(jìn)行一下性能分析吧。

參見

  • 在上面那個(gè)tile的例子中,我們一上來(lái)就為每一種terrain類型創(chuàng)建了一個(gè)實(shí)例贤旷,然后把它保存在了World中广料。這讓使得查找和使用共享實(shí)例變得很簡(jiǎn)單。不過(guò)在很多情況下幼驶,你可能并不想在一開始就去創(chuàng)建所有的享元艾杏。
    如果你不能保證哪些享元是你確實(shí)會(huì)用到的,那就最好在需要的時(shí)候再去創(chuàng)建它們盅藻。而為了利用到共享的好處糜颠,當(dāng)你請(qǐng)求一個(gè)實(shí)例的時(shí)候,你可以先看看自己是否已經(jīng)創(chuàng)建過(guò)一個(gè)萧求。如果是的話,你只需要返回那個(gè)已經(jīng)創(chuàng)建好的實(shí)例顶瞒。
    這通常是意味著你需要將構(gòu)造函數(shù)封裝在一些首先會(huì)查找已存在對(duì)象的接口下夸政。像這樣來(lái)隱藏構(gòu)造函數(shù)的例子使用到了工廠模式。

  • 為了可以返回一個(gè)之前創(chuàng)建過(guò)的享元榴徐,你需要跟蹤一個(gè)存儲(chǔ)池守问,這里保存了所有的已創(chuàng)建對(duì)象。就像池這個(gè)名字暗示的那樣坑资,對(duì)象池可能會(huì)是一個(gè)對(duì)于保存這些對(duì)象很有幫助的模式耗帕。

  • 當(dāng)你使用狀態(tài)模式時(shí),會(huì)經(jīng)常有一些和使用它們的狀態(tài)機(jī)沒有特定關(guān)聯(lián)的狀態(tài)袱贮。而這些狀態(tài)的特性和方法對(duì)你是有一定作用的仿便。在這種情況下,你就可以使用享元模式去在多個(gè)狀態(tài)機(jī)中同時(shí)重用同一個(gè)狀態(tài)實(shí)例攒巍,而這樣是不會(huì)有任何問(wèn)題的嗽仪。


因?yàn)樗接邢蓿g的文字會(huì)有不妥之處柒莉,歡迎大家指正

“本譯文僅供個(gè)人研習(xí)闻坚、欣賞語(yǔ)言之用,謝絕任何轉(zhuǎn)載及用于任何商業(yè)用途兢孝。本譯文所涉法律后果均由本人承擔(dān)窿凤。本人同意簡(jiǎn)書平臺(tái)在接獲有關(guān)著作權(quán)人的通知后,刪除文章跨蟹■ㄊ猓”

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市喷市,隨后出現(xiàn)的幾起案子相种,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 222,000評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件寝并,死亡現(xiàn)場(chǎng)離奇詭異箫措,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)衬潦,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,745評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門斤蔓,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人镀岛,你說(shuō)我怎么就攤上這事弦牡。” “怎么了漂羊?”我有些...
    開封第一講書人閱讀 168,561評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵驾锰,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我走越,道長(zhǎng)椭豫,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,782評(píng)論 1 298
  • 正文 為了忘掉前任旨指,我火速辦了婚禮赏酥,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘谆构。我一直安慰自己裸扶,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,798評(píng)論 6 397
  • 文/花漫 我一把揭開白布搬素。 她就那樣靜靜地躺著呵晨,像睡著了一般。 火紅的嫁衣襯著肌膚如雪蔗蹋。 梳的紋絲不亂的頭發(fā)上何荚,一...
    開封第一講書人閱讀 52,394評(píng)論 1 310
  • 那天,我揣著相機(jī)與錄音猪杭,去河邊找鬼餐塘。 笑死,一個(gè)胖子當(dāng)著我的面吹牛皂吮,可吹牛的內(nèi)容都是我干的戒傻。 我是一名探鬼主播,決...
    沈念sama閱讀 40,952評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼蜂筹,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼需纳!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起艺挪,我...
    開封第一講書人閱讀 39,852評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤不翩,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體口蝠,經(jīng)...
    沈念sama閱讀 46,409評(píng)論 1 318
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡器钟,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,483評(píng)論 3 341
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了妙蔗。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片傲霸。...
    茶點(diǎn)故事閱讀 40,615評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖眉反,靈堂內(nèi)的尸體忽然破棺而出昙啄,到底是詐尸還是另有隱情,我是刑警寧澤寸五,帶...
    沈念sama閱讀 36,303評(píng)論 5 350
  • 正文 年R本政府宣布梳凛,位于F島的核電站,受9級(jí)特大地震影響梳杏,放射性物質(zhì)發(fā)生泄漏伶跷。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,979評(píng)論 3 334
  • 文/蒙蒙 一秘狞、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧蹈集,春花似錦烁试、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,470評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至郭怪,卻和暖如春支示,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背鄙才。 一陣腳步聲響...
    開封第一講書人閱讀 33,571評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工颂鸿, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人攒庵。 一個(gè)月前我還...
    沈念sama閱讀 49,041評(píng)論 3 377
  • 正文 我出身青樓嘴纺,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親浓冒。 傳聞我的和親對(duì)象是個(gè)殘疾皇子栽渴,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,630評(píng)論 2 359

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

  • 1 場(chǎng)景問(wèn)題# 1.1 加入權(quán)限控制## 考慮這樣一個(gè)問(wèn)題,給系統(tǒng)加入權(quán)限控制稳懒,這基本上是所有的應(yīng)用系統(tǒng)都有的功能...
    七寸知架構(gòu)閱讀 2,493評(píng)論 1 57
  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理闲擦,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 134,704評(píng)論 18 139
  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,302評(píng)論 25 707
  • 定義 Flyweight在拳擊比賽中指最輕量級(jí)墅冷,即“蠅量級(jí)”或“雨量級(jí)”纯路。這里選擇使用“享元模式”的意譯,是因?yàn)檫@...
    步積閱讀 1,618評(píng)論 0 2
  • 漸漸的我開始不喜歡把矯情的話掛在嘴邊 不再對(duì)喜歡的人說(shuō)我還在等你 不再對(duì)親密的朋友說(shuō)我特別珍惜你 不再說(shuō)舍不得也不...
    常樂丶閱讀 416評(píng)論 0 0