Kotlin中實現(xiàn)對象深拷貝的3種方式


〇樊拓、Kotlin中的對象拷貝

Kotlin 的 data class 默認提供了一種對象拷貝的方式 , 即 data class 類會生成 copy() 方法, 用于對象的拷貝, 這個方法類似于 java.lang.Objectclone() 方法 ! 值得注意的是: Kotlin 的 data classcopy() 方法 和 java.lang.Objectclone()方法, 都是淺拷貝.

經過測試, 發(fā)現(xiàn) copy() / clone() 方法 返回的對象 的屬性都會指被拷貝對象的屬性 ( 針對引用類型的屬性來說) ! 這就是所謂 \color{red}{淺拷貝}

tips: kotlin用 === 判斷引用是否相同, Java用 == 判斷引用是否相同


下面是對 clone() 和 copy() 方法的測試:

// Kotlin:   (objCopy 是obj.copy() 返回的對象)
obj === objCopy    // false
obj.fieldA === objCopy.fieldA    // true   (fieldA是除了基本類型和String 之外的其他引用類型)

// Java:  (objCopy 是 obj.clone() 返回的對象)
obj == objCopy  // false
obj.fieldA == objCopy.fieldA // true


Kotlin 的 data class 的一些基本概念:
  1. 數據類會將字段定義為private, 然后提供默認的 getter/setter
  2. 數據類自動重寫 equals()、hashCode()冤狡、toString() 方法
  3. 會提供一個 copy() 方法, 方便對象的復制, 但是這個 copy() 方法只是淺拷貝 , 對于字段都是 基本數據類型和String 的類來說, 這就足夠了驰吓。如果類中包含了 基本數據類型和String 之外的引用類型 的字段, 那么必須自己實現(xiàn)深拷貝操作 !!!
  4. 會提供 component#() 方法 (# 為字段編號, 編號從1開始), component# 方法主要用于 解構賦值
    如: val (name,age,sex) = personObject



定義一個 data class

// 數據類會重寫: equals, hashCode, toString 這三個方法 并 自動生成 getter/setter
data class Person(var name:String, var age:Int, var sex:Boolean)

編譯數據類Person后, 可以使用下列命令來查看字節(jié)碼文件的內容:
$ javap -p -c com.stone.demo.basic.Person
-p 輸出private信息
-c 反編譯字節(jié)碼內容


下面是使用javap反編譯一個 data class 類的字節(jié)碼文件的輸出內容, 由于方法的指令部分的內容太多, 已經被刪除了, 只留著方法簽名部分:

// 注意: 需要先編譯出Person.class 文件, 才能使用 javap 反編譯:
// javap -p -c com.stone.demo.basic.Person 

// 輸出內容如下: 
public final class com.stone.demo.basic.Person {
  // 1. 定義類3個字段: 
  private java.lang.String name;
  private int age;
  private boolean sex;

  // 2. primary 構造函數:
  public com.stone.demo.basic.Person(java.lang.String, int, boolean);

  // 3. 字段對應的 getter/setter
  public final java.lang.String getName();
  public final void setName(java.lang.String);
  public final int getAge();
  public final void setAge(int);
  public final boolean getSex();
  public final void setSex(boolean);

  // 4. 每個字段都生成一個對應的 componen#()  方法    --- # 為字段的編號 , # 從 1 開始
  public final java.lang.String component1();
  public final int component2();
  public final boolean component3();
         
  // 5. copy() 方法:  實例方法copy() 和 靜態(tài)方法copy$default()
  public final com.stone.demo.basic.Person copy(java.lang.String, int, boolean);
  public static com.stone.demo.basic.Person copy$default(com.stone.demo.basic.Person, java.lang.String, int, boolean, int, java.lang.Object);
        
  // 6. 重寫  toString, equals, hashCode 方法
  public java.lang.String toString();
    
  // 重寫hashCode方法
  public int hashCode();
      
  // 重寫 equals() 方法
  public boolean equals(java.lang.Object);
}



Kotlin中的基本數據類型 (kotlin中這些數據也都是引用類型的, 分別對應于Java的包裝類):
Boolean, Byte, Char, Short, Int, Long, Float, Double,
另外 String 會使用常量池 , 深拷貝不考慮它 !


一、Kotlin中深拷貝的實現(xiàn)方式一

DeepCopy.kt 文件 , 內容如下:

package com.stone.demo.basic.deepcopy1

class DeepCopy {
}

interface DeepCopyable<out R> {
    fun deepCopy(): R
}

// 一. kotlin實現(xiàn)對象的深度拷貝
// 實現(xiàn)方式: 使用 直接 "創(chuàng)建對象" 的方式實現(xiàn)深度拷貝
// 優(yōu)點: 可以用于 "data-class" 和 "非data-class"
// 缺點: a. 需要實現(xiàn)接口并實現(xiàn)接口中的方法, 使用麻煩
//      b. 對于屬性層級太深的類型, 實現(xiàn)起來會非常麻煩, 而且有可能導致某些屬性無法實現(xiàn)深層拷貝
data class Person(var name:String, var age:Int, var sex: Boolean)
data class Panda (var name:String, var owner:Person) : DeepCopyable<Panda> {
    override fun deepCopy(): Panda {
        return Panda(name, Person(owner.name, owner.age, owner.sex))
    }
}

class A(var name:String)
class B(var field: A) : DeepCopyable<B> {
    override fun deepCopy(): B {
        // 數字, 整型數據類型都有一個緩存 [-128, 127], 拷貝前后的屬性對象會指向同一個
        // 字符串常量, 會放置在常量池中, 拷貝前后的屬性對象也會指向同一個
        // return B(A(field.name.copy)) // 基本類型 和 String 不需要 拷貝 !!
        return B(A(field.name))
    }
}

fun main() {
    // 0x1: 對于普通類
    val b = B(A("哈哈哈"))
    val bCopy = b.deepCopy()
    println(b === bCopy)                // false
    println(b.field === bCopy.field)      // false

    // 0x2. 對于數據類:
    val panda = Panda("團團", Person("張三", 22, true))
    val pandaCopy = panda.copy()
    val pandaDeepCopy = panda.deepCopy()
    println("===================== for copy")
    println(panda === pandaCopy)        // false
    println(panda === pandaDeepCopy)    // false
    println("===================== for owner-field of copy-object")
    println(panda.owner === pandaCopy.owner)        // true  (淺拷貝, 拷貝后的對象的屬性還是會執(zhí)行源對象的屬性)
    println(panda.owner === pandaDeepCopy.owner)    // false
}


二、Kotlin中深拷貝的實現(xiàn)方式二

DeepCopy.kt 文件 , 內容如下:

package com.stone.demo.basic.deepcopy2

import kotlin.reflect.KClass
import kotlin.reflect.full.memberProperties
import kotlin.reflect.full.primaryConstructor

class DeepCopy {
}



// 二. 實現(xiàn)深拷貝的方式2  (成員的類型 必須是 數據類 才能實現(xiàn)深度拷貝)
// https://cloud.tencent.com/developer/article/1941174
// 實現(xiàn): 使用擴展方法 并 通過反射的方式 實現(xiàn)深拷貝
// 優(yōu)點: 使用簡單
// 缺點: 這種方式的深拷貝只對  "data class" 有效,  對于 "非 data class" 則不支 (還是淺拷貝)

fun <T: Any> T.deepCopy():T {
    if(!this::class.isData) { // 不是數據類, 返回對象本身  ( "拷貝" 后的對象指向原對象)
        return this
    }

    // 下面則一長串的邏輯是: 通過反射收集創(chuàng)建 "copy對象" 所需要的參數, 然后通過反射調用primary構造方法創(chuàng)建 "copy對象"

    // 關鍵點: data class 的primary構造方法的參數是可以通過反射獲取到參數名的 且 參數名和類的字段名保持一一對應的關系
    // 通過上面這點, 可以匹配并獲得字段 (primary構造方法的參數的運行時值) 的具體值, 然后構造一個參數列表, 最后調用primary構造方法

    // kotlin.reflect.KFunction<out R>  => 構造函數的Kotlin反射類型
    // 拿到primary構造函數 , 然后在primary構造函數上做一些操作, 最后使用此構造函數創(chuàng)建對象 (參數由 this 經過 反射獲取)
    return this::class.primaryConstructor!!.let { primaryConstructor ->  // primaryConstructor 是個 java.lang.reflect.Constructor 類型的對象

        // primaryConstructor.parameters 是個 List<KParameter> 類型的對象, 即構造函數的參數列表
        // List的map()函數返回的是一個Iterable對象
        // 這里 primaryConstructor.parameters.map() 的返回結果是一個 Iterable<Pair<*,*>> 類型
        primaryConstructor.parameters.map { parameter ->  // parameter 是一個 KParameter 對象, 即構造函數的某一個參數
            // (this::class as KClass<T>).memberProperties   這玩意是某個類成員屬性列表(所有的成員) , 類型為 Collection<KProperty1<T, *>>
            val value = (this::class as KClass<T>).memberProperties.first {
                it.name == parameter.name  // 參數名 和 成員屬性 的名稱相等時, 取得此成員的值  (data class 的 成員字段的名稱 和 primary構造函數的參數的名字是一一對應的)
            }.get(this)

            // 當參數 (也是成員) 的類型是 "數據類" 時, 對參數 (成員) 進行深拷貝 !!
            if((parameter.type.classifier as? KClass<*>)?.isData == true) {
                // 參數 (成員) 為數據類時, 進行深拷貝
                parameter to value?.deepCopy() // 最后一個 (程序的邏輯上的最后一行) 表達式的計算結果作為lambda的返回值時, return 關鍵字可以省略
            } else {
                // 非數據類, 直接返回字段的值 (不做拷貝)
                parameter to value // 普通類型, 進行淺拷貝
                // key to value => 會構造一個Pair對象, Pair對象是Map的元素

                // to 函數 位于 kotlin包中, 在Tuples.kt文件中定義, 是一個 infix 函數, 具體定義如下:
                // public infix fun <A, B> A.to(that: B): Pair<A, B> = Pair(this, that)
                // infix 函數, 為中綴函數, 可以省略 函數調用符  "."  和  表示函數的 "()"
            }
        }.toMap().let(primaryConstructor::callBy) // 通過反射收集創(chuàng)建 "copy對象" 所需要的參數, 然后通過反射調用primary構造方法創(chuàng)建 "copy對象"
        // Iterable<Pair<*,*>> 類型可以轉換為Map對象
    }
}



// a. 數據類
data class A(var name:String)
data class B(var field:A)

// b. 非數據類
class C(var name:String)
class D(var field:C)

fun main() {
    // 1. 數據類
    val b = B(A("哈哈"))
    val bCopy = b.deepCopy()
    println(b === bCopy)                // false
    println(b.field === bCopy.field)      // false

    // 2. 非數據類
    val d = D(C("哈哈"))
    val dCopy = d.deepCopy()
    println(d === dCopy)              // true
    println(d.field === dCopy.field)    // true
}


三辆布、Kotlin中深拷貝的實現(xiàn)方式三

DeepCopy.kt 文件 , 內容如下:

package com.stone.demo.basic.deepcopy3

import kotlin.reflect.KClass
import kotlin.reflect.full.memberProperties
import kotlin.reflect.full.primaryConstructor

class DeepCopy {
}

// 三. DeepCopyable接口 和 數據類 結合的方式 (優(yōu)先使用DeepCopyable接口的deepCopy()方法進行深拷貝)
// 實現(xiàn): 反射調用DeepCopyable接口的deepCopy()方法 或者 反射調用 "data class類" 的copy() 方法  進行深拷貝!
// 優(yōu)點: 可實現(xiàn)深拷貝的對象范圍更大 (實現(xiàn)了DeepCopyable接口的對象, "data class類" 的對象)
// 缺點: 對于既沒有實現(xiàn) DeepCopyable 接口 也不是 "data class類" 的對象, 仍然無法進行深拷貝 !!

// 注意:
// 擴展方法名如果能與DeepCopyable接口的方法 同名, 則對象在調用deepCopy時, 會優(yōu)先調用對象本身定義的方法,
// 而不是調用擴展方法 !!
// 如果要統(tǒng)一調用 擴展方法deepCopy() , 那么需要將 DeepCopyable 接口的方法更改為其他名稱,
// 下面的擴展方法需要更改的地方有兩點:
// 1. 下面的擴展方法中的步驟1中 查找 DeepCopyable 接口的方法的條件 應更改為: it.name = "DeepCopyable中深拷貝方法名"
// 2. 下面的擴展方法中的步驟2中 參數收集的地方, 如果參數實現(xiàn)了DeepCopyable接口, 則參數的深拷貝應改為: value?.DeepCopyable接口中深拷貝方法() , 如 value?.dc()
fun <T : Any> T.deepCopy(): Any? { // 注意: deepCopy的返回值雖然聲明為Any? , 但是, 其真實類型仍然是 T
                                    // 調用deepCopy() 后 可以對其進行轉型
                                    // 如: val a:Type? = typeObject.deepCopy() as? Type
    println("擴展方法 deepCopy() 被調用 !")

    // 1. 對于實現(xiàn)了 DeepCopyable<R> 接口的類, 直接通過反射調用 deepCopy() 方法進行深拷貝
    if(this is DeepCopyable<*>) { // 如果擴展方法 和 DeepCopyable中的方法 同名, 則對象調用深拷貝方法時, 不會調用擴展方法  (這樣一來, 這段判斷就沒有用了 !).
                                    // 因為類本身的深拷貝方法會被優(yōu)先調用.
        // kotlin方式調用有問題: Exception in thread "main" java.lang.IllegalArgumentException: Callable expects 1 arguments, but 0 were provided.
        /*
        return this::class.declaredMemberFunctions.first {
            it.name == "deepCopy"
        }.call()*/

        // 使用Java的反射調用:
        return this::class.java.declaredMethods.first {
            it.name == "deepCopy" // deepCopy 是 DeepCopyable<R> 接口中實現(xiàn)深拷貝的方法
                                    // 如果要同一調用褲子方法來進行深拷貝, 可以將DeepCopyable中的深拷貝方法, 進行更名,
                                    // 如 dc,  那么此處的條件也需要更改為: it.name == "dc"
        }.invoke(this)
    }


    // https://cloud.tencent.com/developer/article/1941174
    // 2. 如果是數據類 (data class) , 則通過反射調用數據類的copy()方法進行深拷貝
    //    注意: 需要考慮對象的字段的類型, 字段的類型分為3種:
    //          1. 實現(xiàn)了 DeepCopyable<R> 接口的字段
    //          2. data class
    //          3. 非上述兩類 (這一大類分為兩種情況: a. 基本數據類型和String --- 這種屬性拷貝是沒有問題的
    //                                          b. 普通的引用類型 (既沒有實現(xiàn)DeepCopyable接口, 也不是data class)  --- 這種屬性只能實現(xiàn)淺拷貝了 )
    if(this::class.isData) {
        // copy()方法的的參數列表 和 primary構造方法的 參數列表是一樣的,
        // 但是copy()方法的參數列表的參數是無法獲取到參數名稱的, primary構造方法的參數是可以獲取到參數名的, 且參數名就是類的字段名稱

        /*
        // data class 的 copy() 方法  (這種方式找到的copy方法, 是kotlin版本的copy, 而調用時調用的字節(jié)碼中的copy方法, 字節(jié)碼中的copy方法是java版本的copy方法)
        val dataClassCopyMethod = this::class.declaredMemberFunctions.first { copyMethod ->
            copyMethod.name == "copy"
        }
        // 字節(jié)碼中的方法簽名是: copy(java.lang.String)
        // 獲取到的方法前面卻是: copy(kotlin.String)
        println("dataClassCopyMethod => $dataClassCopyMethod") // fun com.stone.demo.basic.A.copy(kotlin.String): com.stone.demo.basic.A
        // dataClassCopyMethod 的 類型是 KFunction<*>,  如何將 KFunction<*> 轉換成 Java的java.lang.reflect.Method ??
        // 答案是, 直接找java版本的copy方法, 而不是找kotlin版本的copy方法
         */

        // 查找copy()方法, 使用Java版本的copy()方法
        val dataClassCopyMethod = this::class.java.declaredMethods.first { copyMethod ->
            copyMethod.name == "copy"  // copy 是 數據類中提供的copy()方法
        }
        // println("dataClassCopyMethod => $dataClassCopyMethod") // copy(kotlin.String): com.stone.demo.basic.A

        // data class 的 primary構造方法
        val primaryConstructor = this::class.primaryConstructor

        /*
        // 使用primary構造方法的參數類收集copy()方法的運行時參數, 而不是使用copy()方法的參數來收集copy方法的運行時參數.
        // 原因是: copy() 方法的參數名是獲取不到的, primary構造方法的參數數名稱是可以獲取到的, 且參數名就是字段的名稱
        return dataClassCopyMethod.parameters.map { parameter ->
            // java.util.NoSuchElementException: Collection contains no element matching the predicate.
            val value = (this::class as KClass<T>).memberProperties.first {
                println("it.name=${it.name} , parameter.name=${parameter.name} , parameter.type=${parameter.type} , it.javaClass.typeName=${it.javaClass.typeName}")
                // parameter.name  的值 是 null   => copy() 方法的參數名是獲取不到的, primary構造方法的參數數名稱是可以獲取到的, 且參數名就是字段的名稱
                it.name == parameter.name
            }.get(this)
            parameter to value?.deepCopy1()
        }.toMap().let( dataClassCopyMethod::callBy )
         */

        /*
        return primaryConstructor?.parameters?.map { parameter ->
            // java.util.NoSuchElementException: Collection contains no element matching the predicate.
            val value = (this::class as KClass<T>).memberProperties.first {
                println("it.name=${it.name} , parameter.name=${parameter.name} , parameter.type=${parameter.type} , it.javaClass.typeName=${it.javaClass.typeName}")
                it.name == parameter.name
            }.get(this)

            if(value is DeepCopyable1<*>) {
                parameter to value?.deepCopy()
            } else if(value != null && value::class.isData) { // 如果是數據類, 就遞歸調用 deepCopy1()
                println("value => $value")
                // java.lang.IllegalArgumentException: No argument provided for a required parameter: instance parameter of fun com.stone.demo.basic.A.copy(kotlin.String): com.stone.demo.basic.A
                // data class A(val name: String) 類的copy()方法, 編譯后的簽名是: copy(java.lang.String)  , 而不是 copy(kotlin.String)
                parameter to value.deepCopy1()
            } else {
                // 此處再做一下處理, 否則出現(xiàn): // java.lang.IllegalArgumentException: No argument provided for a required parameter: instance parameter of fun com.stone.demo.basic.A.copy(kotlin.String): com.stone.demo.basic.A
                if(value is String) {
                    parameter to java.lang.String(value)
                } else {
                    parameter to value
                }

                // Exception in thread "main" java.lang.IllegalArgumentException:
                // No argument provided for a required parameter: instance parameter of fun com.stone.demo.basic.A.copy(kotlin.String): com.stone.demo.basic.A
            }
        }?.toMap()?.let( dataClassCopyMethod::callBy )  // kotlin 反射方法的調用方式
         */

        val params = primaryConstructor?.parameters?.map { parameter ->
            val value = (this::class as KClass<T>).memberProperties.first {
                // println("it.name=${it.name} , parameter.name=${parameter.name} , parameter.type=${parameter.type} , it.javaClass.typeName=${it.javaClass.typeName}")
                it.name == parameter.name
            }.get(this)

            // 數據類的字段分為三種:
            // 1. 實現(xiàn)了 DeepCopyable<R> 接口的字段
            // 2. data class
            // 3. 非上述兩類 (這一大類分為兩種情況:
            //      a. 基本數據類型和String --- 這種屬性拷貝是沒有問題的
            //      b. 普通的引用類型 (既沒有實現(xiàn)DeepCopyable接口, 也不是data class)  --- 這種屬性只能實現(xiàn)淺拷貝了
            //
            // 對于三種類型的字段分別實現(xiàn)相對于的參數收集邏輯 !!
            if(value is DeepCopyable<*>) {
                value?.deepCopy()   // 如果 要同一使用 擴展方法來進行深拷貝, 則 DeepCopyable 方法需要更名為其他名字,
                                    // 且此處改為:  value?.DeepCopyable接口中進行深拷貝的方法()
                                    // 例如: DeepCopyable 接口中深拷貝方法為 dc(), 則此處 應為: value?.dc()
            } else if(value != null && value::class.isData) { // 如果是數據類, 就遞歸調用 deepCopy()
                // println("value => $value")
                // java.lang.IllegalArgumentException: No argument provided for a required parameter: instance parameter of fun com.stone.demo.basic.A.copy(kotlin.String): com.stone.demo.basic.A
                // data class A(val name: String) 類的copy()方法, 編譯后的簽名是: copy(java.lang.String)  , 而不是 copy(kotlin.String)
                value.deepCopy() // 這里value.deepCopy() 是遞歸調用當前 (擴展) 方法
            } else {
                value   // 返回屬性本身 (淺拷貝)
                // Exception in thread "main" java.lang.IllegalArgumentException:
                // No argument provided for a required parameter: instance parameter of fun com.stone.demo.basic.A.copy(kotlin.String): com.stone.demo.basic.A
            }
        }?.toTypedArray()

        // 注意: return 不能少, 否則會 調用最后的 "return this"
        return if(params == null) {
            dataClassCopyMethod.invoke(this)
        } else {
            // https://blog.csdn.net/xlh1191860939/article/details/82109086
            // 將 Array<T> 轉換為可變參數   *arrayVar
            dataClassCopyMethod.invoke(this, *params)
        }
    }

    // 3. 既沒有實現(xiàn) DeepCopyable<R> 接口, 也不是  "data class 類,  直接返回對象本身 (淺拷貝 --- 引用拷貝 --- 只是在棧上拷貝了一個引用,這個引用仍然指向源對象 !)
    return this
}


// a. 實現(xiàn)了 DeepCopyable 接口的類
interface DeepCopyable<out R> {
    fun deepCopy(): R
}

data class Person(var name:String, var age:Int, var sex: Boolean)
data class Panda (var name:String, var owner:Person) : DeepCopyable<Panda> {
    override fun deepCopy(): Panda {
        return Panda(name, Person(owner.name, owner.age, owner.sex))
    }
}

// b. 數據類
data class A(var name:String)
data class B(var field:A)

// c. 非數據類
class C(var name:String)
class D(var field:C)

// 測試代碼:
fun main() {
    // 1. 實現(xiàn)了 DeepCopyable 接口的類的對象
    val panda = Panda("圓圓", Person("張三", 32, true))
    val pandaDeepCopy = panda.deepCopy()    // 此處實際上是調用 DeepCopyable 的 deepCopy()方法, 而不是調用擴展方法 deepCopy()
                                            // 由輸出信息可以看出 !
                                            // 擴展函數中的 println("擴展方法 deepCopy() 被調用 !") 此句代碼并未被調用 !
    println("======================= test for DeepCopyable")
    println(panda === pandaDeepCopy)             // false
    println(panda.owner === pandaDeepCopy.owner) // false

    // 2. data class 類 的對象
    println("======================= test for data class")
    val b = B(A("哈哈"))
    val bCopy = b.deepCopy() as B
    println(b === bCopy)           // false
    println(b.field === bCopy.field) // false

    // 3. 非上述兩類對象
    println("======================= test for other")
    val d = D(C("嘻嘻"))
    val dCopy = d.deepCopy() as D
    println(d === dCopy)            // true
    println(d.field === dCopy.field)  // true
}


References:Kotlin | 實現(xiàn)數據類(data)深拷貝

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市茶鉴,隨后出現(xiàn)的幾起案子谚殊,更是在濱河造成了極大的恐慌,老刑警劉巖蛤铜,帶你破解...
    沈念sama閱讀 218,682評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異丛肢,居然都是意外死亡围肥,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,277評論 3 395
  • 文/潘曉璐 我一進店門蜂怎,熙熙樓的掌柜王于貴愁眉苦臉地迎上來穆刻,“玉大人,你說我怎么就攤上這事杠步∏馕埃” “怎么了?”我有些...
    開封第一講書人閱讀 165,083評論 0 355
  • 文/不壞的土叔 我叫張陵幽歼,是天一觀的道長朵锣。 經常有香客問我,道長甸私,這世上最難降的妖魔是什么诚些? 我笑而不...
    開封第一講書人閱讀 58,763評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮皇型,結果婚禮上诬烹,老公的妹妹穿的比我還像新娘。我一直安慰自己弃鸦,他們只是感情好绞吁,可當我...
    茶點故事閱讀 67,785評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著唬格,像睡著了一般家破。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上购岗,一...
    開封第一講書人閱讀 51,624評論 1 305
  • 那天员舵,我揣著相機與錄音,去河邊找鬼藕畔。 笑死马僻,一個胖子當著我的面吹牛,可吹牛的內容都是我干的注服。 我是一名探鬼主播韭邓,決...
    沈念sama閱讀 40,358評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼措近,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了女淑?” 一聲冷哼從身側響起瞭郑,我...
    開封第一講書人閱讀 39,261評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎鸭你,沒想到半個月后屈张,有當地人在樹林里發(fā)現(xiàn)了一具尸體,經...
    沈念sama閱讀 45,722評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡袱巨,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,900評論 3 336
  • 正文 我和宋清朗相戀三年阁谆,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片愉老。...
    茶點故事閱讀 40,030評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡场绿,死狀恐怖,靈堂內的尸體忽然破棺而出嫉入,到底是詐尸還是另有隱情焰盗,我是刑警寧澤,帶...
    沈念sama閱讀 35,737評論 5 346
  • 正文 年R本政府宣布咒林,位于F島的核電站熬拒,受9級特大地震影響,放射性物質發(fā)生泄漏垫竞。R本人自食惡果不足惜梦湘,卻給世界環(huán)境...
    茶點故事閱讀 41,360評論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望件甥。 院中可真熱鬧捌议,春花似錦、人聲如沸引有。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,941評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽譬正。三九已至宫补,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間曾我,已是汗流浹背粉怕。 一陣腳步聲響...
    開封第一講書人閱讀 33,057評論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留抒巢,地道東北人贫贝。 一個月前我還...
    沈念sama閱讀 48,237評論 3 371
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親稚晚。 傳聞我的和親對象是個殘疾皇子崇堵,可洞房花燭夜當晚...
    茶點故事閱讀 44,976評論 2 355

推薦閱讀更多精彩內容