Java程序員必須知道的常用序列化技術(shù)及選型,Protobuf 原理詳解

基于 socket 進(jìn)行對象傳輸
先舉個(gè)簡單的例子,基于我們前面幾次課程的只是,寫一個(gè) socket 通信的代碼

User.java

public class User {
  private String name;
  public String getName() {
  return name;
}
public void setName(String name) {
  this.name = name;
}
}

SocketServerProvider.java

public class SocketServerProvider {
    public static void main(String[] args) throws
            IOException {
        ServerSocket serverSocket = null;
        BufferedReader in = null;
        try {
            serverSocket = new ServerSocket(8080);
            Socket socket = serverSocket.accept();
            ObjectInputStream objectInputStream =
                    new ObjectInputStream(socket.getInputStream());
            User user=(User)objectInputStream.readObject();
            System.out.println(user);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (in != null) {
                try {
                    in.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (serverSocket != null) {
                serverSocket.close();
            }
        }
    }
}

SocketClientConsumer.java

public class SocketClientConsumer {
    public static void main(String[] args) {
        Socket socket = null;
        ObjectOutputStream out = null;
        try {
            socket = new Socket("127.0.0.1", 8080);
            User user = new User();
            out = new
                    ObjectOutputStream(socket.getOutputStream());
            out.writeObject(user);
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (out != null) {
                try {
                    out.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (socket != null) {
                try {
                    socket.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

運(yùn)行結(jié)果

這段代碼運(yùn)行以后,能夠?qū)崿F(xiàn) Java 對象的正常傳輸嗎? 很顯然,會報(bào)錯(cuò)


如何解決報(bào)錯(cuò)的問題呢?
對 User 這個(gè)對象實(shí)現(xiàn)一個(gè) Serializable 接口,再次運(yùn)行就可以看到對象能夠正常傳輸了

public class User implements Serializable {
    private String name;
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
}

了解序列化的意義

我們發(fā)現(xiàn)對 User 這個(gè)類增加一個(gè) Serializable,就可以解決 Java 對象的網(wǎng)絡(luò)傳輸問題。這就是今天想給大家講解的序列化這塊的意義。

Java 平臺允許我們在內(nèi)存中創(chuàng)建可復(fù)用的 Java 對象,但一般情況下,只有當(dāng) JVM 處于運(yùn)行時(shí),這些對象才可能存在,即,這些對象的生命周期不會比 JVM 的生命周期更長。但在現(xiàn)實(shí)應(yīng)用中,就可能要求在 JVM 停止運(yùn)行之后能夠保存(持久化)指定的對象,并在將來重新讀取被保存的對象。 Java 對象序列化就能夠幫助我們實(shí)現(xiàn)該功能。

簡單來說

序列化是把對象的狀態(tài)信息轉(zhuǎn)化為可存儲或傳輸?shù)男问竭^程,也就是把對象轉(zhuǎn)化為字節(jié)序列的過程稱為對象的序列化。

反序列化是序列化的逆向過程,把字節(jié)數(shù)組反序列化為對象,把字節(jié)序列恢復(fù)為對象的過程成為對象的反序列化

序列化的高階認(rèn)識

簡單認(rèn)識一下 Java 原生序列化

前面的代碼中演示了,如何通過 JDK 提供了 Java 對象的序列化方式實(shí)現(xiàn)對象序列化傳輸,主要通過輸出流java.io.ObjectOutputStream和對象輸入流java.io.ObjectInputStream來實(shí)現(xiàn)。

java.io.ObjectOutputStream:表示對象輸出流 , 它的 writeObject(Object obj)方法可以對參數(shù)指定的 obj 對象進(jìn)行序列化,把得到的字節(jié)序列寫到一個(gè)目標(biāo)輸出流中。

java.io.ObjectInputStream:表示對象輸入流 ,它的 readObject()方法源輸入流中讀取字節(jié)序列,再把它們反序列化成為一個(gè)對象,并將其返回。

需要注意的是,被序列化的對象需要實(shí)現(xiàn) java.io.Serializable 接口

  • serialVersionUID 的作用
    在 IDEA 中通過如下設(shè)置可以生成 serializeid


字面意思上是序列化的版本號,凡是實(shí)現(xiàn) Serializable 接口的類都有一個(gè)表示序列化版本標(biāo)識
符的靜態(tài)變量

演示步驟

  1. 先將 user 對象序列化到文件中
  2. 然后修改 user 對象,增加 serialVersionUID 字段
  3. 然后通過反序列化來把對象提取出來
  4. 演示預(yù)期結(jié)果:提示無法反序列化

結(jié)論
Java 的序列化機(jī)制是通過判斷類的 serialVersionUID 來驗(yàn)證版本一致性的。在進(jìn)行反序列化時(shí), JVM 會把傳來的字節(jié)流中的 serialVersionUID 與本地相應(yīng)實(shí)體類的 serialVersionUID 進(jìn)行比較,如果相同就認(rèn)為是一致的,可以進(jìn)行反序列化,否則就會出現(xiàn)序列化版本不一致的異常,即是 InvalidCastException。

從結(jié)果可以看出,文件流中的 class 和 classpath 中的 class,也就是修改過后的 class,不兼容了,處于安全機(jī)制考慮,程序拋出了錯(cuò)誤,并且拒絕載入。從錯(cuò)誤結(jié)果來看,如果沒有為指定的 class 配置 serialVersionUID,那么 java 編譯器會自動(dòng)給這個(gè) class 進(jìn)行一個(gè)摘要算法,類似于指紋算法,只要這個(gè)文件有任何改動(dòng),得到的 UID 就會截然不同的,可以保證在這么多類中,這個(gè)編號是唯一的。所以,由于沒有顯指定 serialVersionUID,編譯器又為我們生成了一個(gè) UID,當(dāng)然和前面保存在文件中的那個(gè)不會一樣了,于是就出現(xiàn)了 2 個(gè)序列化版本號不一致的錯(cuò)誤。因此,只要我們自己指定了 serialVersionUID,就可以在序列化后,去添加一個(gè)字段,或者方法,而不會影響到后期的還原,還原后的對象照樣可以使用,而且還多了方法或者屬性可以用。

tips: serialVersionUID 有兩種顯示的生成方式:
一是默認(rèn)的 1L,比如: private static final long serialVersionUID = 1L;
二是根據(jù)類名、接口名、成員方法及屬性等來生成一個(gè) 64 位的哈希字段,當(dāng)實(shí)現(xiàn) java.io.Serializable 接口的類沒有顯式地定義一個(gè) serialVersionUID 變量時(shí)候, Java 序列化機(jī)制會根據(jù)編譯的 Class 自動(dòng)生成一個(gè) serialVersionUID 作序列化版本比較用,這種情況下,如果 Class 文件(類名,方法明等)沒有發(fā)生變化(增加空格,換行,增加注釋等等),就算再編譯多次, serialVersionUID 也不會變化的。

  • Transient 關(guān)鍵字
    Transient 關(guān)鍵字的作用是控制變量的序列化,在變量聲明前加上該關(guān)鍵字,可以阻止該變量被序列化到文件中,在被反序列化后, transient 變量的值被設(shè)為初始值,如 int 型的是0,對象型的是 null。

  • writeObject 和 readObject 原理
    writeObject 和 readObject 是兩個(gè)私有的方法,他們是什么時(shí)候被調(diào)用的呢?從運(yùn)行結(jié)果來看,它確實(shí)被調(diào)用。而且他們并不存在于 Java.lang.Object,也沒有在 Serializable 中去聲明。
    我們唯一的猜想應(yīng)該還是和 ObjectInputStream 和 ObjectOutputStream 有關(guān)系,所以基于這個(gè)入口去看看在哪個(gè)地方有調(diào)用



    從源碼層面來分析可以看到, readObject 是通過反射來調(diào)用的。
    其實(shí)我們可以在很多地方看到 readObject 和 writeObject 的使用,比如 HashMap。

Java 序列化的一些簡單總結(jié)

  1. Java 序列化只是針對對象的狀態(tài)進(jìn)行保存,至于對象中的方法,序列化不關(guān)心
  2. 當(dāng)一個(gè)父類實(shí)現(xiàn)了序列化,那么子類會自動(dòng)實(shí)現(xiàn)序列化,不需要顯示實(shí)現(xiàn)序列化接口
  3. 當(dāng)一個(gè)對象的實(shí)例變量引用了其他對象,序列化這個(gè)對象的時(shí)候會自動(dòng)把引用的對象也進(jìn)
    行序列化(實(shí)現(xiàn)深度克隆)
  4. 當(dāng)某個(gè)字段被申明為 transient 后,默認(rèn)的序列化機(jī)制會忽略這個(gè)字段
  5. 被申明為 transient 的字段,如果需要序列化,可以添加兩個(gè)私有方法: writeObject 和
    readObject

分布式架構(gòu)下常見序列化技術(shù)

初步了解了 Java 序列化的知識以后,我們又得回到分布式架構(gòu)中,了解序列化的發(fā)展過程。

隨著分布式架構(gòu)、微服務(wù)架構(gòu)的普及。服務(wù)與服務(wù)之間的通信成了最基本的需求。這個(gè)時(shí)候,我們不僅需要考慮通信的性能,也需要考慮到語言多元化問題。所以,對于序列化來說,如何去提升序列化性能以及解決跨語言問題,就成了一個(gè)重點(diǎn)考慮的問題。

由于 Java 本身提供的序列化機(jī)制存在兩個(gè)問題

  1. 序列化的數(shù)據(jù)比較大,傳輸效率低
  2. 其他語言無法識別和對接

以至于在后來的很長一段時(shí)間,基于 XML 格式編碼的對象序列化機(jī)制成為了主流,一方面解決了多語言兼容問題,另一方面比二進(jìn)制的序列化方式更容易理解。以至于基于 XML的 SOAP協(xié)議及對應(yīng)的 WebService 框架在很長一段時(shí)間內(nèi)成為各個(gè)主流開發(fā)語言的必備的技術(shù)。

再到后來,基于 JSON 的簡單文本格式編碼的 HTTP REST 接口又基本上取代了復(fù)雜的 Web Service 接口,成為分布式架構(gòu)中遠(yuǎn)程通信的首要選擇。但是 JSON 序列化存儲占用的空間大、性能低等問題,同時(shí)移動(dòng)客戶端應(yīng)用需要更高效的傳輸數(shù)據(jù)來提升用戶體驗(yàn)。在這種情況下與語言無關(guān)并且高效的二進(jìn)制編碼協(xié)議就成為了大家追求的熱點(diǎn)技術(shù)之一。首先誕生的一個(gè)開源的二進(jìn)制序列化框架-MessagePack。它比 google 的 Protocol Buffers 出現(xiàn)得還要早。

簡單了解各種序列化技術(shù)

  • XML 序列化框架介紹
    XML 序列化的好處在于可讀性好,方便閱讀和調(diào)試。但是序列化以后的字節(jié)碼文件比較大,而且效率不高,適用于對性能不高,而且 QPS 較低的企業(yè)級內(nèi)部系統(tǒng)之間的數(shù)據(jù)交換的場景,同時(shí) XML 又具有語言無關(guān)性,所以還可以用于異構(gòu)系統(tǒng)之間的數(shù)據(jù)交換和協(xié)議。比如我們熟知的Webservice,就是采用 XML 格式對數(shù)據(jù)進(jìn)行序列化的。 XML 序列化/反序列化的實(shí)現(xiàn)方式有很多,熟知的方式有 XStream 和 Java 自帶的 XML 序列化和反序列化兩種。

  • JSON 序列化框架
    JSON(JavaScript Object Notation)是一種輕量級的數(shù)據(jù)交換格式,相對于 XML 來說, JSON的字節(jié)流更小,而且可讀性也非常好。現(xiàn)在 JSON 數(shù)據(jù)格式在企業(yè)運(yùn)用是最普遍的。
    JSON 序列化常用的開源工具有很多:

  1. Jackson (https://github.com/FasterXML/jackson
  2. 阿里開源的 FastJson (https://github.com/alibaba/fastjon
  3. Google 的 GSON (https://github.com/google/gson)

這幾種 json 序列化工具中, Jackson 與 fastjson 要比 GSON 的性能要好,但是 Jackson、GSON 的穩(wěn)定性要比 Fastjson 好。而 fastjson 的優(yōu)勢在于提供的 api 非常容易使用。

  • Hessian 序列化框架
    Hessian 是一個(gè)支持跨語言傳輸?shù)亩M(jìn)制序列化協(xié)議,相對于 Java 默認(rèn)的序列化機(jī)制來說,Hessian 具有更好的性能和易用性,而且支持多種不同的語言。
    實(shí)際上 Dubbo 采用的就是 Hessian 序列化來實(shí)現(xiàn),只不過 Dubbo 對 Hessian 進(jìn)行了重構(gòu),性能更高。

  • Avro 序列化
    Avro 是一個(gè)數(shù)據(jù)序列化系統(tǒng),設(shè)計(jì)用于支持大批量數(shù)據(jù)交換的應(yīng)用。它的主要特點(diǎn)有:支持二進(jìn)制序列化方式,可以便捷,快速地處理大量數(shù)據(jù);動(dòng)態(tài)語言友好, Avro 提供的機(jī)制使動(dòng)態(tài)語言可以方便地處理 Avro 數(shù)據(jù)。

  • kyro 序列化框架
    Kryo 是一種非常成熟的序列化實(shí)現(xiàn),已經(jīng)在 Hive、 Storm)中使用得比較廣泛,不過它不能跨語言. 目前 dubbo 已經(jīng)在 2.6 版本支持 kyro 的序列化機(jī)制。它的性能要優(yōu)于之前的hessian2

  • Protobuf 序列化框架

Protobuf 是 Google 的一種數(shù)據(jù)交換格式,它獨(dú)立于語言、獨(dú)立于平臺。 Google 提供了多種語言來實(shí)現(xiàn),比如 Java、 C、 Go、 Python,每一種實(shí)現(xiàn)都包含了相應(yīng)語言的編譯器和庫文件,Protobuf 是一個(gè)純粹的表示層協(xié)議,可以和各種傳輸層協(xié)議一起使用。

Protobuf 使用比較廣泛,主要是空間開銷小和性能比較好,非常適合用于公司內(nèi)部對性能要求高的 RPC 調(diào)用。 另外由于解析性能比較高,序列化以后數(shù)據(jù)量相對較少,所以也可以應(yīng)用在對象的持久化場景中。

但是要使用 Protobuf 會相對來說麻煩些,因?yàn)樗凶约旱恼Z法,有自己的編譯器,如果需要用到的話必須要去投入成本在這個(gè)技術(shù)的學(xué)習(xí)中。

protobuf 有個(gè)缺點(diǎn)就是要傳輸?shù)拿恳粋€(gè)類的結(jié)構(gòu)都要生成對應(yīng)的 proto 文件,如果某個(gè)類發(fā)生修改,還得重新生成該類對應(yīng)的 proto 文件。

Protobuf 序列化的原理
那么接下來著重分析一下 protobuf 的序列化原理,前面說過它的優(yōu)勢是空間開銷小,性能也相對較好。它里面用到的一些算法還是值得我們?nèi)W(xué)習(xí)的。

protobuf 的基本應(yīng)用

使用 protobuf 開發(fā)的一般步驟是

  1. 配置開發(fā)環(huán)境,安裝 protocol compiler 代碼編譯器
  2. 編寫.proto 文件,定義序列化對象的數(shù)據(jù)結(jié)構(gòu)
  3. 基于編寫的.proto 文件,使用 protocol compiler 編譯器生成對應(yīng)的序列化/反序列化工具類
  4. 基于自動(dòng)生成的代碼,編寫自己的序列化應(yīng)用

Protobuf 案例演示

下載 protobuf 工具,https://github.com/google/protobuf/releases, 找到 protoc-3.5.1-win32.zip。

編寫 proto 文件

syntax="proto2";
package com.gupaoedu.serial;
option java_package =
"com.gupaoedu.serial";
option java_outer_classname="UserProtos";
message User {
  required string name=1;
  required int32 age=2;
}

數(shù)據(jù)類型:string / bytes / bool / int32(4 個(gè)字節(jié))/ int64 / float / double / enum 枚舉類 / message 自定義類
修飾符:required 表示必填字段;optional 表示可選字段;repeated 可重復(fù),表示集合。
1, 2, 3, 4 需要在當(dāng)前范圍內(nèi)是唯一的,表示順序。

生成實(shí)體類
【.\protoc.exe --java_out=./ ./user.proto】

實(shí)現(xiàn)序列化

<dependency>
   <groupId>com.google.protobuf</groupId>
   <artifactId>protobuf-java</artifactId>
   <version>RELEASE</version>
 </dependency>
UserProtos.User user=UserProtos.User.newBuilder()
  .setName("Mic")
  .setAge(18).build();
ByteString bytes=user.toByteString();
System.out.println(bytes);
UserProtos.User nUser=UserProtos.User.parseFrom(bytes);
System.out.println(nUser);

protobuf 序列化原理
我們可以把序列化以后的數(shù)據(jù)打印出來看看結(jié)果


我們可以看到,序列化出來的數(shù)字基本看不懂,但是序列化以后的數(shù)據(jù)確實(shí)很小,那我們接下來帶大家去了解一下底層的原理。

正常來說,要達(dá)到最小的序列化結(jié)果,一定會用到壓縮的技術(shù),而 protobuf 里面用到了兩種壓縮算法,一種是 varint,另一種是 zigzag。

varint
先說第一種,我們先來看 age=300 這個(gè)數(shù)字是如何被壓縮的



這兩個(gè)字節(jié)字節(jié)分別的結(jié)果是: -84 、 2
-84 怎么計(jì)算來的呢? 我們知道在二進(jìn)制中表示負(fù)數(shù)的方法,高位設(shè)置為 1, 并且是對應(yīng)數(shù)字的二進(jìn)制取反以后再計(jì)算補(bǔ)碼表示(補(bǔ)碼是反碼+1)
所以如果要反過來計(jì)算

  1. 【補(bǔ)碼】 10101100 -1 得到 10101011
  2. 【反碼】 01010100 得到的結(jié)果為 84. 由于高位是 1,表示負(fù)數(shù)所以結(jié)果為-84

字符如何轉(zhuǎn)化為編碼
“Mic”這個(gè)字符,需要根據(jù) ASCII 對照表轉(zhuǎn)化為數(shù)字。
M =77、 i=105、 c=99
所以結(jié)果為 77 105 99
大家肯定有個(gè)疑問,這里的結(jié)果為什么直接就是 ASCII 編碼的值呢?怎么沒有做壓縮呢?有沒有同學(xué)能夠回答出來。
原因是, varint 是對字節(jié)碼做壓縮,但是如果這個(gè)數(shù)字的二進(jìn)制只需要一個(gè)字節(jié)表示的時(shí)候,其實(shí)最終編碼出來的結(jié)果是不會變化的。

還有兩個(gè)數(shù)字, 3 和 16 代表什么呢?那就要了解 protobuf 的存儲格式了

存儲格式
protobuf 采用 T-L-V 作為存儲方式


tag 的計(jì)算方式是 field_number(當(dāng)前字段的編號) << 3 | wire_type
比如 Mic 的字段編號是 1 ,類型 wire_type 的值為 2 所以 : 1 <<3 | 2 =10
age=300 的字段編號是 2,類型 wire_type 的值是 0, 所以 : 2<<3|0 =16

第一個(gè)數(shù)字 10,代表的是 key,剩下的都是 value。

負(fù)數(shù)的存儲
在計(jì)算機(jī)中,負(fù)數(shù)會被表示為很大的整數(shù),因?yàn)橛?jì)算機(jī)定義負(fù)數(shù)符號位為數(shù)字的最高位,所以如果采用 varint 編碼表示一個(gè)負(fù)數(shù),那么一定需要 5 個(gè)比特位。所以在 protobuf 中通過sint32/sint64 類型來表示負(fù)數(shù),負(fù)數(shù)的處理形式是先采用 zigzag 編碼(把符號數(shù)轉(zhuǎn)化為無符號數(shù)),在采用 varint 編碼。
sint32: (n << 1) ^ (n >> 31)
sint64: (n << 1) ^ (n >> 63)
比如存儲一個(gè)(-300)的值
-300
原碼: 0001 0010 1100
取反: 1110 1101 0011
加 1 : 1110 1101 0100
n<<1: 整體左移一位,右邊補(bǔ) 0 -> 1101 1010 1000
n>>31: 整體右移 31 位,左邊補(bǔ) 1 -> 1111 1111 1111
n<<1 ^ n >>31
1101 1010 1000 ^ 1111 1111 1111 = 0010 0101 0111
十進(jìn)制: 0010 0101 0111 = 599
varint 算法: 從右往做,選取 7 位,高位補(bǔ) 1/0(取決于字節(jié)數(shù))
得到兩個(gè)字節(jié)
1101 0111 0000 0100
-41 、 4

總結(jié)

Protocol Buffer 的性能好,主要體現(xiàn)在 序列化后的數(shù)據(jù)體積小 & 序列化速度快,最終使得
傳輸效率高,其原因如下:
序列化速度快的原因:
a. 編碼 / 解碼 方式簡單(只需要簡單的數(shù)學(xué)運(yùn)算 = 位移等等)
b. 采用 Protocol Buffer 自身的框架代碼 和 編譯器 共同完成
序列化后的數(shù)據(jù)量體積小(即數(shù)據(jù)壓縮效果好)的原因:
a. 采用了獨(dú)特的編碼方式,如 Varint、 Zigzag 編碼方式等等
b. 采用 T - L - V 的數(shù)據(jù)存儲方式:減少了分隔符的使用 & 數(shù)據(jù)存儲得緊湊

序列化技術(shù)的選型

  • 技術(shù)層面
  1. 序列化空間開銷,也就是序列化產(chǎn)生的結(jié)果大小,這個(gè)影響到傳輸?shù)男阅?/li>
  2. 序列化過程中消耗的時(shí)長,序列化消耗時(shí)間過長影響到業(yè)務(wù)的響應(yīng)時(shí)間
  3. 序列化協(xié)議是否支持跨平臺,跨語言。因?yàn)楝F(xiàn)在的架構(gòu)更加靈活,如果存在異構(gòu)系統(tǒng)通信需求,那么這個(gè)是必須要考慮的
  4. 可擴(kuò)展性/兼容性,在實(shí)際業(yè)務(wù)開發(fā)中,系統(tǒng)往往需要隨著需求的快速迭代來實(shí)現(xiàn)快速更新,這就要求我們采用的序列化協(xié)議基于良好的可擴(kuò)展性/兼容性,比如在現(xiàn)有的序列化數(shù)據(jù)結(jié)構(gòu)中新增一個(gè)業(yè)務(wù)字段,不會影響到現(xiàn)有的服務(wù)
  5. 技術(shù)的流行程度,越流行的技術(shù)意味著使用的公司多,那么很多坑都已經(jīng)淌過并且得到了解決,技術(shù)解決方案也相對成熟
  6. 學(xué)習(xí)難度和易用性
  • 選型建議
  1. 對性能要求不高的場景,可以采用基于 XML 的 SOAP 協(xié)議;
  2. 對性能和間接性有比較高要求的場景,那么 Hessian、 Protobuf、 Thrift、 Avro 都可以;
  3. 基于前后端分離,或者獨(dú)立的對外的 api 服務(wù),選用 JSON 是比較好的,對于調(diào)試、可讀性都很不錯(cuò);
  4. Avro 設(shè)計(jì)理念偏于動(dòng)態(tài)類型語言,那么這類的場景使用 Avro 是可以的。

各個(gè)序列化技術(shù)的性能比較
這 個(gè) 地 址 有 針 對 不 同 序 列 化 技 術(shù) 進(jìn) 行 性 能 比 較 :https://github.com/eishay/jvmserializers/wiki

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,237評論 6 537
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,957評論 3 423
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 177,248評論 0 382
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,356評論 1 316
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 72,081評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,485評論 1 324
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,534評論 3 444
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,720評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,263評論 1 335
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 41,025評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 43,204評論 1 371
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,787評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,461評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,874評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,105評論 1 289
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 51,945評論 3 395
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 48,205評論 2 375