減少GC

減少垃圾的產(chǎn)生量

可以使用一些技術(shù)來幫助我們減少代碼中生成的垃圾量

1.緩存

如果我們的代碼重復(fù)調(diào)用產(chǎn)生堆分配的函數(shù)包晰,然后丟棄結(jié)果憨琳,這將產(chǎn)生不必要的垃圾锰蓬。 對(duì)此邻储,我們應(yīng)該存儲(chǔ)對(duì)這些對(duì)象的引用并復(fù)用它們赋咽。 這種技術(shù)被稱為緩存

下面的函數(shù)每次調(diào)用都會(huì)引起堆分配吨娜,因?yàn)槊看握{(diào)用都會(huì)生成一個(gè)新的數(shù)組脓匿。

void OnTriggerEnter(Collider other)
{
    Renderer[] allRenderers = FindObjectsOfType<Renderer>();
    ExampleFunction(allRenderers);
}

下面的代碼只會(huì)有一次堆分配,因?yàn)閿?shù)組創(chuàng)建賦值后被緩存起來了宦赠。緩存的數(shù)組可以復(fù)用因而不會(huì)產(chǎn)生垃圾陪毡。

private Renderer[] allRenderers;

void Start()
{
    allRenderers = FindObjectsOfType<Renderer>();
}
void OnTriggerEnter(Collider other)
{
    ExampleFunction(allRenderers);
}

2.不要在頻繁調(diào)用的函數(shù)中分配

如果我們需要在MonoBehaviour中分配堆內(nèi)存米母,在頻繁調(diào)用的函數(shù)里分配是最糟糕的。比如 每幀調(diào)用的函數(shù)Update()LateUpdate()毡琉,在這些地方分配铁瞒,垃圾將非常快的累積桅滋。我們應(yīng)該盡可能在Start() 或 Awake() 里緩存這些對(duì)象的引用慧耍,或者確保分配內(nèi)存的代碼只在需要的時(shí)候被運(yùn)行。

讓我們來看個(gè)簡(jiǎn)單的例子,下面的代碼在每次 Update()調(diào)用時(shí)都會(huì)調(diào)用一個(gè)引起堆分配的函數(shù),會(huì)非呈幔快的產(chǎn)生垃圾

void Update()
{
    ExampleGarbageGeneratingFunction(transform.position.x);
}

簡(jiǎn)單修改后,可以確保產(chǎn)生堆分配的函數(shù)只在transform.position.x 的值改變時(shí)才被調(diào)用.這樣只在需要的時(shí)候產(chǎn)生堆分配而不會(huì)每幀都產(chǎn)生.

private float previousTransformPositionX;
void Update()
{
    float transformPositionX = transform.position.x;
    if (transformPositionX != previousTransformPositionX)
    {
        ExampleGarbageGeneratingFunction(transformPositionX);
        previousTransformPositionX = transformPositionX;
    }
}

另一個(gè)在 Update()函數(shù)中減少垃圾內(nèi)存產(chǎn)生量的方法是使用計(jì)時(shí)器.這適用于那些會(huì)產(chǎn)生垃圾內(nèi)存的代碼需要被頻繁調(diào)用又不需要每幀調(diào)用的地方

下面的示例代碼,產(chǎn)生垃圾內(nèi)存的函數(shù)每幀被調(diào)用

void Update()
{
    ExampleGarbageGeneratingFunction();
}

下面的代碼,使用一個(gè)計(jì)時(shí)器來保證產(chǎn)生垃圾內(nèi)存的函數(shù)每秒只被調(diào)一次

private float timeSinceLastCalled;

private float delay = 1f;

void Update()
{
    timeSinceLastCalled += Time.deltaTime;
    if (timeSinceLastCalled > delay)
    {
        ExampleGarbageGeneratingFunction();
        timeSinceLastCalled = 0f;
    }
}

像這樣對(duì)頻繁調(diào)用函數(shù)的小改動(dòng),可以顯著的減少垃圾內(nèi)存的產(chǎn)生量

3.清空容器

創(chuàng)建容器類會(huì)引起堆分配,如果在代碼中發(fā)現(xiàn)多次創(chuàng)建同一個(gè)容器變量,則應(yīng)該緩存該容器引用并在重復(fù)創(chuàng)建的地方使用 Clear()操作來替代

下面的示例中每次 *new *操作都會(huì)產(chǎn)生一次堆分配

void Update()
{
    List myList = new List();
    PopulateList(myList);
}

下面的示例中,只在容器被創(chuàng)建或者擴(kuò)容時(shí)才會(huì)有堆分配,顯著減少了垃圾內(nèi)存的產(chǎn)生量

private List myList = new List();
void Update()
{
    myList.Clear();
    PopulateList(myList);
}

4.對(duì)象池

即使減少了腳本中的堆分配,在運(yùn)行時(shí)大量對(duì)象的創(chuàng)建和銷毀依然會(huì)引起GC問題. 對(duì)象池是一種通過重用對(duì)象而不是重復(fù)創(chuàng)建和銷毀對(duì)象來減少分配和釋放的技術(shù).對(duì)象池在游戲中廣泛使用,最適合于頻繁產(chǎn)生和銷毀類似對(duì)象的情況;,例如,當(dāng)槍射擊子彈時(shí).

引起不必要堆分配的常見原因

我們知道局部的,值類型的變量被分配在棧上,其他的都在堆上分配.但是很多情況下的堆分配可能讓人驚訝.我們來看看一些不必要的堆分配的常見原因,并考慮如何最好地減少這些蜂绎。

1.字符串

在C#中,字符串是引用類型,而不是值類型,盡管它們似乎保持字符串的“值”. 這意味著創(chuàng)建和丟棄字符串會(huì)產(chǎn)生垃圾.由于字符串常用在很多代碼中,所以這些垃圾可能累積笋鄙。

C#中的字符串也是不可變的,這意味著它們的值在第一次創(chuàng)建之后不能再被更改怪瓶。 每次我們操縱一個(gè)字符串(例如萧落,通過使用+運(yùn)算符來連接兩個(gè)字符串),Unity將創(chuàng)建一個(gè)包含更新值的新字符串洗贰,并丟棄舊字符串找岖。 這會(huì)產(chǎn)生垃圾。

我們可以遵循一些簡(jiǎn)單的規(guī)則敛滋,將字符串產(chǎn)生的垃圾減至最少许布。 我們來看看這些規(guī)則,然后看一下應(yīng)用它們的例子绎晃。

  • 減少不必要的字符串創(chuàng)建蜜唾。 如果多次使用相同的字符串值,應(yīng)該創(chuàng)建一次該字符串并緩存該值庶艾。
  • 減少不必要的字符串操作袁余。 例如,如果有一個(gè)經(jīng)常更新的Text組件咱揍,并且包含一個(gè)連接的字符串颖榜,可以考慮將它分成兩個(gè)Text組件。
  • 如果必須在運(yùn)行時(shí)構(gòu)建字符串煤裙,應(yīng)該使用StringBuilder類掩完。 StringBuilder類用于創(chuàng)建沒有堆分配的字符串,并且在連接復(fù)雜字符串時(shí)減少生成的垃圾量硼砰。
  • 當(dāng)不在需要調(diào)試時(shí),立即刪除對(duì)Debug.Log()的調(diào)用且蓬。即使沒有輸出任何內(nèi)容,對(duì)Debug.Log()的調(diào)用依然會(huì)被執(zhí)行夺刑。調(diào)用Debug.Log()創(chuàng)建和處理至少一個(gè)字符串缅疟,所以如果我們的游戲包含許多這些調(diào)用分别,垃圾會(huì)累積

來看一個(gè)低效使用字符串而產(chǎn)生不必要垃圾的代碼的例子。 在下面的代碼中存淫,在Update()中創(chuàng)建一個(gè)連接“TIME:”與浮點(diǎn)計(jì)時(shí)器的值的字符串來顯示分?jǐn)?shù)耘斩,這產(chǎn)生了不必要的垃圾。

public Text timerText;
private float timer;

void Update()
{
    timer += Time.deltaTime;
    timerText.text = "TIME:" + timer.ToString();
}

下面我們做些改進(jìn)桅咆。 我們把單詞“TIME:”放在一個(gè)單獨(dú)的文本組件中括授,并在Start()中設(shè)置它的值。 這樣在Update()中岩饼,我們不再需要連接字符串荚虚。 可以大大減少垃圾的產(chǎn)生。

public Text timerHeaderText;
public Text timerValueText;
private float timer;

void Start()
{
    timerHeaderText.text = "TIME:";
}

void Update()
{
    timerValueText.text = timer.toString();
}

2.unity函數(shù)調(diào)用

重要的是要注意籍茧,每當(dāng)我們調(diào)用不是自己寫的代碼時(shí)版述,無論是在Unity中還是在插件中,都可能會(huì)產(chǎn)生垃圾寞冯。 調(diào)用一些Unity函數(shù)會(huì)產(chǎn)生堆分配渴析,因此應(yīng)謹(jǐn)慎使用以避免產(chǎn)生不必要的垃圾。

并沒有一個(gè)應(yīng)該避免使用的函數(shù)列表吮龄。 每個(gè)函數(shù)在某些情況下都是有用的俭茧,而在其他情況下則不太有用。所以最好仔細(xì)分析我們的游戲漓帚,確定垃圾的產(chǎn)生位置并仔細(xì)思考如何處理母债。 在某些情況下,可以緩存函數(shù)的結(jié)果; 在某些情況下尝抖,可以降低調(diào)用函數(shù)的頻率; 在其他情況下毡们,最好重構(gòu)代碼以使用不同的函數(shù)。 話雖如此牵署,我們來看幾個(gè)常見的會(huì)導(dǎo)致堆分配 的Unity函數(shù)漏隐,并考慮如何更好地處理它們。

每次訪問返回值為數(shù)組的Unity函數(shù)時(shí)奴迅,都會(huì)創(chuàng)建一個(gè)新的數(shù)組青责,并將其作為返回值傳遞給我們。 這種行為并不總是顯而易見的或可預(yù)期的取具,特別是當(dāng)函數(shù)是訪問器的時(shí)候(例如 Mesh.normals)脖隶。

下面的代碼中,每次循環(huán)迭代都會(huì)生成一個(gè)新的數(shù)組

void ExampleFunction()
{
    for (int i = 0; i < myMesh.normals.Length; i++)
    {
        Vector3 normal = myMesh.normals[i];
    }
}

這種情況下很容易減少分配:我們可以簡(jiǎn)單地緩存對(duì)數(shù)組的引用暇检。 這樣可以只創(chuàng)建一個(gè)數(shù)組产阱,并相應(yīng)地減少了產(chǎn)生的垃圾量。

下面的代碼演示了這一點(diǎn)块仆。 在這種情況下构蹬,我們?cè)谘h(huán)之前調(diào)用Mesh.normals并緩存引用王暗,這樣就只創(chuàng)建一個(gè)數(shù)組。

void ExampleFunction()
{
    Vector3[] meshNormals = myMesh.normals;
    for (int i = 0; i < meshNormals.Length; i++)
    {
        Vector3 normal = meshNormals[i];
    }
}

訪問GameObject.name或GameObject.tag也會(huì)有堆分配庄敛。 這兩個(gè)都是返回新字符串的訪問器俗壹,這意味著調(diào)用這些函數(shù)會(huì)產(chǎn)生垃圾。 緩存該值可能是有用的藻烤,但在這種情況下绷雏,可以使用相關(guān)的Unity函數(shù)。 要檢查一個(gè)GameObject的標(biāo)簽的值而不產(chǎn)生垃圾怖亭,我們可以使用 GameObject.CompareTag()涎显。

下面的示例代碼中,訪問GameObject.tag會(huì)產(chǎn)生垃圾內(nèi)存:

private string playerTag = "Player";

void OnTriggerEnter(Collider other)
{
    bool isPlayer = other.gameObject.tag == playerTag;
}

如果使用 GameObject.CompareTag()兴猩,則該函數(shù)不會(huì)產(chǎn)生垃圾:

private string playerTag = "Player";

void OnTriggerEnter(Collider other)
{
    bool isPlayer = other.gameObject.CompareTag(playerTag);
}

GameObject.CompareTag并不是唯一的期吓,很多Unity的函數(shù)都有無堆分配的替代版本。比如可以使用Input.GetTouch()Input.touchCount 替換Input.touches峭跳, 或者使用Physics.SphereCastNonAlloc() 替換 Physics.SphereCastAll()膘婶。

3.裝箱

裝箱是指當(dāng)一個(gè)值類型變量被用作一個(gè)引用類型變量時(shí)所執(zhí)行的操作。當(dāng)我們將值類型的變量(如int或float)傳遞給具有object類型參數(shù)的函數(shù)時(shí)蛀醉,通常會(huì)發(fā)生裝箱,如Object.Equals()函數(shù)衅码。

例如拯刁,函數(shù)String.Format()接受一個(gè)string和一個(gè)object參數(shù)。 當(dāng)我們傳遞一個(gè)string和一個(gè)int時(shí)逝段,int就會(huì)被裝箱垛玻。 下面的代碼包含了一個(gè)裝箱的例子:

void ExampleFunction()
{
    int cost = 5;
    string displayString = String.Format("Price: {0} gold", cost);
}

裝箱會(huì)產(chǎn)生垃圾源于其后臺(tái)操作。當(dāng)一個(gè)值類型變量被裝箱時(shí)奶躯,Unity在堆上創(chuàng)建一個(gè)臨時(shí)的System.Object來包裝值類型變量帚桩。 一個(gè)System.Object是一個(gè)引用類型的變量,所以當(dāng)這個(gè)臨時(shí)對(duì)象被處理掉時(shí)會(huì)產(chǎn)生垃圾嘹黔。

裝箱是不必要的堆分配的常見原因账嚎。 即使我們不在我們的代碼中直接裝箱變量,我們可能也會(huì)使用導(dǎo)致裝箱的插件儡蔓,裝箱也可能發(fā)生在其他函數(shù)的后臺(tái)郭蕉。 最好的做法是盡可能避免裝箱,并刪除導(dǎo)致裝箱的任何函數(shù)調(diào)用喂江。

4.協(xié)程

調(diào)用StartCoroutine()會(huì)產(chǎn)生少量的垃圾召锈,因?yàn)閁nity必須創(chuàng)建一些管理協(xié)程的實(shí)例的類。 所以获询,當(dāng)游戲在交互時(shí)或在性能熱點(diǎn)時(shí)應(yīng)該限制對(duì)StartCoroutine()的調(diào)用涨岁。 為了減少這種方式產(chǎn)生的垃圾拐袜,必須在性能熱點(diǎn)運(yùn)行的協(xié)程應(yīng)該提前啟動(dòng),當(dāng)使用可能包含對(duì)StartCoroutine()的延遲調(diào)用的嵌套協(xié)程時(shí)梢薪,我們應(yīng)特別小心蹬铺。

協(xié)程中的yield語句不會(huì)自己產(chǎn)生堆分配; 然而,我們傳遞給yield語句的值可能會(huì)產(chǎn)生不必要的堆分配沮尿。 例如丛塌,以下代碼會(huì)產(chǎn)生垃圾:

yield return 0;

該代碼產(chǎn)生垃圾,因?yàn)閕nt變量0被裝箱畜疾。 在這種情況下赴邻,如果我們希望只是等待一個(gè)幀而不會(huì)導(dǎo)致任何堆分配,那么最好的方法是使用以下代碼:

yield return null;

協(xié)程的另一個(gè)常見錯(cuò)誤是在多次使用相同的值時(shí)使用了new操作啡捶, 例如姥敛,以下代碼將在循環(huán)迭代時(shí)每次都重復(fù)創(chuàng)建和銷毀一個(gè)WaitForSeconds對(duì)象:

while (!isComplete)
{
    yield return new WaitForSeconds(1f);
}

如果緩存和復(fù)用WaitForSeconds對(duì)象,就能減少垃圾的產(chǎn)生量瞎暑,請(qǐng)看以下示例代碼:

WaitForSeconds delay = new WaitForSeconds(1f);

while (!isComplete)
{
    yield return delay;
}

如果我們的代碼由于協(xié)程而產(chǎn)生大量垃圾彤敛,我們可能考慮使用除協(xié)程之外的其他東西來重構(gòu)我們的代碼。 重構(gòu)代碼是一個(gè)復(fù)雜的問題了赌,每個(gè)項(xiàng)目都是獨(dú)一無二的墨榄,但是有一些常用的手段或許對(duì)協(xié)程問題有幫助。 例如勿她,如果我們主要使用協(xié)同程序來管理時(shí)間袄秩,我們可以簡(jiǎn)單地在一個(gè)Update()函數(shù)中記錄時(shí)間。 如果我們主要使用協(xié)同程序來控制游戲中發(fā)生的事情的順序逢并,我們可以創(chuàng)建某種消息系統(tǒng)來允許對(duì)象進(jìn)行通信之剧。 一個(gè)方法不能解決所有問題,但是有必要記住砍聊,在代碼中可以有多種方法來實(shí)現(xiàn)相同的事情背稼。

5.foreach循環(huán)

在Unity5.5之前的版本中,使用foreach遍歷數(shù)組之外的所有集合玻蝌,在循環(huán)終止時(shí)都會(huì)產(chǎn)生垃圾蟹肘,這是因?yàn)槠浜笈_(tái)的裝箱操作。當(dāng)循環(huán)開始并且循環(huán)終止時(shí)灶伊,一個(gè)System.Object對(duì)象被分配在堆上疆前。 Unity 5.5中已修復(fù)此問題。

在5.5之前的Unity版本中聘萨,以下代碼中的循環(huán)會(huì)生成垃圾:

void ExampleFunction(List listOfInts)
{
    foreach (int currentInt in listOfInts)
    {
         DoSomething(currentInt);
    }
}

如果我們無法升級(jí)我們的Unity版本竹椒,則有一個(gè)簡(jiǎn)單的解決方案來解決這個(gè)問題。 for和while循環(huán)不會(huì)在后臺(tái)引起裝箱米辐,因此不會(huì)產(chǎn)生任何垃圾胸完。 當(dāng)?shù)皇菙?shù)組的集合時(shí)书释,我們應(yīng)該優(yōu)先使用它們。

下面的代碼不會(huì)產(chǎn)生垃圾:

void ExampleFunction(List listOfInts)
{
    for (int i = 0; i < listOfInts.Count; i ++)
    {
        int currentInt = listOfInts[i];
        DoSomething(currentInt);
    }
}

6.函數(shù)引用

函數(shù)引用赊窥,無論是引用匿名函數(shù)還是命名函數(shù)爆惧,都是Unity中的引用類型變量。 它們將導(dǎo)致堆分配锨能。 將匿名函數(shù)轉(zhuǎn)換為 閉包(匿名函數(shù)可在其創(chuàng)建時(shí)訪問范圍中的變量)顯著增加了內(nèi)存使用量和堆分配數(shù)量扯再。

函數(shù)引用和閉包如何分配內(nèi)存的精確細(xì)節(jié)因平臺(tái)和編譯器設(shè)置而異,但是如果GC是一個(gè)問題址遇,那么最好在游戲過程中盡量減少使用函數(shù)引用和閉包熄阻。 <u><u>這個(gè)Unity性能最佳實(shí)踐指南</u></u> 在這個(gè)主題上有更多的技術(shù)細(xì)節(jié)。

7.LINQ和正則表達(dá)式

LINQ和正則表達(dá)式由于在后臺(tái)會(huì)有裝箱操作而產(chǎn)生垃圾倔约。在有性能要求的時(shí)候最好不使用秃殉。 同樣,<u><u>這個(gè)Unity性能最佳實(shí)踐指南</u></u> 提供了有關(guān)此主題的更多技術(shù)細(xì)節(jié)浸剩。

8.構(gòu)建代碼以最小化GC的影響

代碼的構(gòu)建方式可能會(huì)影響GC钾军。即使代碼中沒有堆分配,也有可能增加GC的負(fù)擔(dān)绢要。

可能增加GC的負(fù)擔(dān)之一是要求它檢查它不應(yīng)該檢查的東西吏恭。Structs是值類型變量,但是如果有一個(gè)包含引用類型變量的struct重罪,那么垃圾收集器必須檢查整個(gè)結(jié)構(gòu)體砸泛。 如果有大量這樣的結(jié)構(gòu)體,那么垃圾回收器將增加大量額外的工作蛆封。

在這個(gè)例子中,下面的struct包含了一個(gè)引用類型的字符串勾栗。 現(xiàn)在在垃圾回收器運(yùn)行時(shí)必須檢查結(jié)構(gòu)體的整個(gè)數(shù)組惨篱。

public struct ItemData
{
    public string name;
    public int cost;
    public Vector3 position;
}
private ItemData[] itemData;

在這個(gè)例子中,我們將數(shù)據(jù)存儲(chǔ)在單獨(dú)的數(shù)組中围俘。 當(dāng)垃圾收集器運(yùn)行時(shí)砸讳,它只需要檢查字符串?dāng)?shù)組,并且可以忽略其他數(shù)組界牡。 這減少了垃圾收集器的工作簿寂。

private string[] itemNames;
private int[] itemCosts;
private Vector3[] itemPositions;

另一個(gè)可能增加GC負(fù)擔(dān)的操作是使用不必要的對(duì)象引用,當(dāng)垃圾收集器搜索對(duì)堆上對(duì)象的引用時(shí)宿亡,它必須檢查代碼中的每個(gè)當(dāng)前對(duì)象引用常遂。 更少的對(duì)象引用意味著更少的工作量,即使我們不減少堆上的對(duì)象總數(shù)挽荠。

在這個(gè)例子中克胳,我們有一個(gè)類填充一個(gè)對(duì)話框平绩。 當(dāng)用戶查看對(duì)話框時(shí),會(huì)顯示另一個(gè)對(duì)話框漠另。 我們的代碼包含對(duì)應(yīng)該顯示的DialogData的下一個(gè)實(shí)例的引用捏雌,這意味著垃圾回收器必須在其操作中檢查此引用:

public class DialogData
{
    private DialogData nextDialog;
    public DialogData GetNextDialog()
    {
        return nextDialog;
    }
}

這里我們重構(gòu)下代碼,以便它返回一個(gè)用于查找下一個(gè)DialogData實(shí)例的標(biāo)識(shí)符笆搓,而不是實(shí)例本身性湿。 這不是一個(gè)對(duì)象引用,所以它不會(huì)增加垃圾收集器所花費(fèi)的時(shí)間满败。

public class DialogData
{
    private int nextDialogID;
    public int GetNextDialogID()
    {
        return nextDialogID;
    }
}

這是個(gè)小例子肤频。 然而,如果我們的游戲中有許多包含對(duì)其他對(duì)象引用的對(duì)象葫录,那么我們可以通過以這種方式重構(gòu)代碼來大大降低堆的復(fù)雜性着裹。

定時(shí)GC

1.手動(dòng)強(qiáng)制GC

最后,我們可能希望自己觸發(fā)GC米同。 如果我們知道堆內(nèi)存已被分配但不再使用(例如骇扇,如果我們的代碼在加載資源時(shí)生成垃圾),并且我們知道垃圾收集凍結(jié)不會(huì)影響播放器(例如面粮,當(dāng)加載界面還顯示時(shí))少孝,我們可以使用以下代碼請(qǐng)求GC:

System.GC.Collect();

這將強(qiáng)制運(yùn)行GC,在我們方便的時(shí)候釋放未使用的內(nèi)存熬苍。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末稍走,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子柴底,更是在濱河造成了極大的恐慌婿脸,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,755評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件柄驻,死亡現(xiàn)場(chǎng)離奇詭異狐树,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)鸿脓,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門抑钟,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人野哭,你說我怎么就攤上這事在塔。” “怎么了拨黔?”我有些...
    開封第一講書人閱讀 165,138評(píng)論 0 355
  • 文/不壞的土叔 我叫張陵蛔溃,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我,道長(zhǎng)城榛,這世上最難降的妖魔是什么揪利? 我笑而不...
    開封第一講書人閱讀 58,791評(píng)論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮狠持,結(jié)果婚禮上疟位,老公的妹妹穿的比我還像新娘。我一直安慰自己喘垂,他們只是感情好甜刻,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,794評(píng)論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著正勒,像睡著了一般得院。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上章贞,一...
    開封第一講書人閱讀 51,631評(píng)論 1 305
  • 那天祥绞,我揣著相機(jī)與錄音,去河邊找鬼鸭限。 笑死蜕径,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的败京。 我是一名探鬼主播兜喻,決...
    沈念sama閱讀 40,362評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼赡麦!你這毒婦竟也來了朴皆?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,264評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤泛粹,失蹤者是張志新(化名)和其女友劉穎遂铡,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體晶姊,經(jīng)...
    沈念sama閱讀 45,724評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡忧便,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,900評(píng)論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了帽借。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,040評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡超歌,死狀恐怖砍艾,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情巍举,我是刑警寧澤脆荷,帶...
    沈念sama閱讀 35,742評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響蜓谋,放射性物質(zhì)發(fā)生泄漏梦皮。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,364評(píng)論 3 330
  • 文/蒙蒙 一桃焕、第九天 我趴在偏房一處隱蔽的房頂上張望剑肯。 院中可真熱鬧,春花似錦观堂、人聲如沸让网。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,944評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽滥壕。三九已至口予,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間因篇,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,060評(píng)論 1 270
  • 我被黑心中介騙來泰國(guó)打工笔横, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留竞滓,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,247評(píng)論 3 371
  • 正文 我出身青樓狠裹,卻偏偏與公主長(zhǎng)得像虽界,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子涛菠,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,979評(píng)論 2 355

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