Java利用最優(yōu)二叉樹實(shí)現(xiàn)哈夫曼編碼的壓縮和解壓

什么是哈夫曼編碼

哈夫曼編碼(Huffman Coding)箍邮,又稱霍夫曼編碼,是一種編碼方式芋绸,哈夫曼編碼是可變字長編碼(VLC)的一種媒殉。Huffman于1952年提出一種編碼方法,該方法完全依據(jù)字符出現(xiàn)概率來構(gòu)造異字頭的平均長度最短的碼字摔敛,有時(shí)稱之為最佳編碼,一般就叫做Huffman編碼(有時(shí)也稱為霍夫曼編碼)

為什么哈夫曼編碼能夠?qū)崿F(xiàn)文件的壓縮

如果我們使用的定長編碼方例如ASCII碼全封,8-bit定長編碼马昙,使用8位(一個(gè)字節(jié))代表一個(gè)字符,就會(huì)出現(xiàn)很多的byte出現(xiàn)了相同刹悴,所以我們考慮一種新的編碼方式行楞,變長編碼的方式,原理很簡單土匀,以前是使用的8位表示一個(gè)字符子房,現(xiàn)在該用不固定長度,確定長度的依據(jù)是每個(gè)字符出現(xiàn)的次數(shù)就轧,越是高頻的字符其所用的bit(二進(jìn)制位數(shù))越短证杭,這樣就實(shí)現(xiàn)了整個(gè)文本的變短,

//舉例我們有這么一個(gè)字符串:
String content = "abbcccddddeeeee";

當(dāng)我們把這段文字發(fā)送給別人時(shí)妒御,會(huì)轉(zhuǎn)換成一段字節(jié)數(shù)組長成下面這個(gè)樣子

字節(jié)數(shù)組:
[97, 98, 98, 99, 99, 99, 100, 100, 100, 100, 101, 101, 101, 101, 101]
二進(jìn)制:
110000111000101100010110001111000111100011110010011001001100100110010011001011100101110010111001011100101

使用哈夫曼編碼過后
先統(tǒng)計(jì)文本每個(gè)字符的 a : 1 b:2  c:3  d:4  e:5
每個(gè)字符對應(yīng)的二進(jìn)制 a->1110  b->1111  c->110   d->10 e->0
二進(jìn)制:
010011011000000101010101111111111
字節(jié)數(shù)組:
[77, -127, 85, -1, 1] 

要想學(xué)習(xí)哈夫曼編碼先了解哈夫曼樹的基本概念

  • 路徑和路徑長度:在一棵樹中解愤,從節(jié)點(diǎn)往下可以達(dá)到的孩子或者孫子節(jié)點(diǎn)之間的通路,從樹根節(jié)點(diǎn)到葉子節(jié)點(diǎn)的長度稱為路徑長度乎莉,從從根節(jié)點(diǎn)到第L層節(jié)點(diǎn)到路徑長度為L-1
  • 節(jié)點(diǎn)的權(quán)及帶權(quán)路徑長度:給節(jié)點(diǎn)值我們就叫做節(jié)點(diǎn)的權(quán)值送讲,節(jié)點(diǎn)帶權(quán)路徑的為: 路徑長度 * 權(quán)值
  • 一整個(gè)樹的帶權(quán)路徑長度會(huì)等于所有葉子節(jié)點(diǎn)的帶權(quán)路徑長度之和奸笤,稱為WPL(weighted path length)
  • WPL最小時(shí)就叫做哈夫曼樹,因此我們構(gòu)建哈夫曼樹時(shí)應(yīng)該將最大權(quán)值的節(jié)點(diǎn)放在根節(jié)點(diǎn)附近
非最優(yōu)二叉樹1.png

非最優(yōu)二叉樹2
哈夫曼樹.png

1哼鬓、壓縮算法實(shí)現(xiàn)部分

基本思路

  • 構(gòu)造樹節(jié)點(diǎn)node
  • 用生成的節(jié)點(diǎn)node構(gòu)造哈夫曼樹
  • 生成哈夫曼編碼表
  • 用哈夫曼編碼表生成壓縮后的字節(jié)數(shù)組

構(gòu)造樹節(jié)點(diǎn)node

這是node類實(shí)現(xiàn)了Comparable借口监右,并且重寫了compareTo可以后續(xù)用于所有node根據(jù)權(quán)值排序



public class Node implements Comparable<Node>{
    Byte data;    //存放數(shù)據(jù)本身
    int weight;  //權(quán)值
    Node left;  //指向左子節(jié)點(diǎn)
    Node right; //指向右子節(jié)點(diǎn)
    public Node(Byte data, int weight) {
        this.data = data;
        this.weight = weight;
    }
    @Override
    public int compareTo(Node o) {
        // TODO Auto-generated method stub
        return this.weight-o.weight;
    }
    @Override
    public String toString() {
        return "Node [data=" + data + ", weight=" + weight + "]";
    }
}

根據(jù)文本信息轉(zhuǎn)換成Byte數(shù)組,再創(chuàng)建一個(gè)HashMap 遍歷整個(gè)byte數(shù)組key保存當(dāng)前的byte的值异希,value用于統(tǒng)計(jì)相同字節(jié)出現(xiàn)的頻率健盒,再把這個(gè)HashMap中的節(jié)點(diǎn)轉(zhuǎn)換成Node節(jié)點(diǎn)

public static void main(String[] args) {
       // TODO Auto-generated method stub
       String content = "abbcccddddeeeee";
       byte[] contentBytes = content.getBytes();
   List<Node> nodes = getNodes(bytes);
   }

private static List<Node> getNodes(byte[] bytes) {
       // 創(chuàng)建Arrlist
       ArrayList<Node> nodes = new ArrayList<Node>();
       // 遍歷bytes,統(tǒng)計(jì)byte出現(xiàn)的次數(shù)->map
       Map<Byte, Integer> counts = new HashMap();
       for (byte b : bytes) {
           if (!counts.containsKey(b)) {
               counts.put(b, 1);
           } else {
               counts.put(b, counts.get(b) + 1);
           }
       }
       // 把每一個(gè)鍵對轉(zhuǎn)成一個(gè)Node對象宠互,并加入到nodes集合中
       for (Map.Entry<Byte, Integer> entry : counts.entrySet()) {
           nodes.add(new Node(entry.getKey(), entry.getValue()));
       }
       return nodes;
   }

用生成的節(jié)點(diǎn)node構(gòu)造哈夫曼樹

  • 權(quán)值越大味榛,帶權(quán)路徑長度應(yīng)該最短越接近root節(jié)點(diǎn),原因是用0和1分別表示二叉樹的左右字節(jié)點(diǎn)可達(dá)路徑予跌,
  • 對傳入的list中的node從小到大進(jìn)行排序搏色,拿出第一個(gè)最小的兩個(gè)元素,構(gòu)成新的節(jié)點(diǎn)放入list中
  • 對新的節(jié)點(diǎn)不賦值,權(quán)重是兩個(gè)券册,把它的左右節(jié)點(diǎn)初始化為最小的兩個(gè)元素频轿,權(quán)重是兩個(gè)是兩個(gè)子節(jié)點(diǎn)的和
  • 重復(fù)以上的步驟直到list中的元素小2時(shí)候停止
示例 String content = "abbcccddddeeeee";
a : 1 b:2  c:3  d:4  e:5
步驟1
步驟2
步驟3
步驟4

具體代碼如下:

    // 可以通過list創(chuàng)建哈夫曼樹
    /**
     * @param nodes 要處理的nodes
     * @return 返回root節(jié)點(diǎn)
     */
    public static Node createHuffmanTree(List<Node> nodes) {
        // 傳入的list中大于1個(gè)節(jié)點(diǎn)時(shí)候才構(gòu)建二叉樹
        while (nodes.size() > 1) {
            // 對集合里面的元素進(jìn)行排序
            Collections.sort(nodes);
            // 取出權(quán)值最小的節(jié)點(diǎn)
            Node left = nodes.get(0);
            Node right = nodes.get(1);
            // 構(gòu)建一個(gè)新的二叉樹節(jié)點(diǎn),并且初始化它的值為null
            Node parent = new Node(null, left.weight + right.weight);
            // 把兩個(gè)節(jié)點(diǎn)掛在一個(gè)非子節(jié)點(diǎn)上
            parent.left = left;
            parent.right = right;
            // 移除左右兩個(gè)節(jié)點(diǎn)
            nodes.remove(left);
            nodes.remove(right);
            // 把這個(gè)非葉子節(jié)點(diǎn)加入到存放到nodes中
            nodes.add(parent);
        }
        return nodes.get(0);
    }

構(gòu)建哈夫曼樹的輸出結(jié)果

Node [data=null, weight=15]
Node [data=null, weight=6]
Node [data=99, weight=3]
Node [data=null, weight=3]
Node [data=97, weight=1]
Node [data=98, weight=2]
Node [data=null, weight=9]
Node [data=100, weight=4]
Node [data=101, weight=5]

生成哈夫曼編碼表

首先要?jiǎng)?chuàng)建huffmanCodes哈夫曼編碼表 烁焙,表的key - node.data(a,b,c) value - 0101航邢,重載getCodes函數(shù)定義一個(gè)StringBuilder用于字符串010101的連接,遞歸處理之前生成好的哈夫曼樹骄蝇,往左走拼接 '0' ,往右走拼接'1'只有當(dāng)我們遍歷到葉子節(jié)點(diǎn)才把它加入huffmanCodes中

    // 聲明哈夫曼編碼表膳殷,static修飾,在多個(gè)方法中共同使用
    public static Map<Byte, String> huffmanCodes = new HashMap<Byte, String>(); 

    // 生成哈夫曼樹對應(yīng)的哈夫曼編碼
    // 思路:
    // 將哈夫曼編碼存放在map中比較合適 Map<Byte,String>
    // e->0  d->10 c->110 b->1111 a->1110 

    // 為了調(diào)用方便九火,重載getCodes
    private static Map<Byte, String> getCodes(Node root) {
        if (root == null) {
            return null;
        }
    //處理只有root節(jié)點(diǎn)的特殊情況
        if (root.left == null && root.right == null) {
            huffmanCodes.put(root.data, "0");
        }
        // 在生成哈夫曼編碼表時(shí)赚窃,需要去拼接路徑,定義一個(gè)StringBuilder 存儲(chǔ)葉子節(jié)點(diǎn)的路徑
        StringBuilder builder = new StringBuilder();
        // 處理左子樹
        getCodes(root.left, "0", builder);
        // 處理右子樹
        getCodes(root.right, "1", builder);
        return huffmanCodes;
    }

    /**
     * 功能:將傳入的node節(jié)點(diǎn)的所有葉子節(jié)點(diǎn)的哈夫曼得到岔激,并放入huffmanCodes中
     * 
     * @param node    //傳入節(jié)點(diǎn)
     * @param code    //路徑:左子節(jié)點(diǎn)是0 右子節(jié)點(diǎn)1
     * @param builder //用于拼接路徑
     */
    private static void getCodes(Node node, String code, StringBuilder builder) {
        // 重建StringBuilder是為了防治地址引用一值勒极,保持生成字符不共用同一字符串
        StringBuilder builder2 = new StringBuilder(builder);
        // 將code加入到StringBuilder
        builder2.append(code);
        if (node != null) { // 如果為空不進(jìn)行處理
            // 判斷當(dāng)前是葉子還是非葉子節(jié)點(diǎn)
            if (node.data == null) {
                // 遞歸處理
                // 左邊遞歸
                getCodes(node.left, "0", builder2);
                // 右邊遞歸
                getCodes(node.right, "1", builder2);
            } else {
                // 找到了葉子節(jié)點(diǎn)
                huffmanCodes.put(node.data, builder2.toString());
            }
        }
    }

用哈夫曼編碼表生成壓縮后的字節(jié)數(shù)組

最后一步就是用我們生成好的哈夫曼編碼表對我們的全部文本進(jìn)行生成處理過后的字節(jié)數(shù)組,遍歷整個(gè)字節(jié)數(shù)組用字節(jié)數(shù)組的每個(gè)數(shù)據(jù)去hashmap中匹配010101生成二進(jìn)制字符串虑鼎,因?yàn)楣蚵幋a表對應(yīng)的二進(jìn)制是可變長的辱匿,所以最后字符串的長度可能就不是8的倍數(shù),要進(jìn)行特殊處理多加一個(gè)字節(jié)炫彩,最后一個(gè)字節(jié)如果不滿8bit匾七,我們在這里要用一個(gè)static int endLen記錄下它的長度,在后面逆向解壓中將要用到 媒楼, 再遍歷整個(gè)字符串?dāng)?shù)組用Byte數(shù)組來封裝好整個(gè)字符串乐尊,1Byte=8bit, 具體的代碼如下:

    // 編寫一個(gè)方法划址,將字符串對應(yīng)的byte[]數(shù)組扔嵌,通過生成哈夫曼編碼表限府,返回一個(gè)哈夫曼編碼處理后的byte[]數(shù)組
    /**
     * @param bytes       原始數(shù)據(jù)要處理的byte數(shù)組
     * @param huffmanCode //哈夫曼編碼表
     * @return //返回處理后的byte[]
     */
    public static byte[] zip(byte[] bytes, Map<Byte, String> huffmanCode) {
        // 利用huffmanCodes將byte轉(zhuǎn)成哈夫曼對應(yīng)的字符串
        StringBuilder builder = new StringBuilder("");
        // 遍歷數(shù)組
        for (byte b : bytes) {
            builder.append(huffmanCodes.get(b));
        }
        // 將"10101000..." 轉(zhuǎn)成byte[]
        // 統(tǒng)計(jì)返回的哈夫曼編碼有多長
        int len = (builder.length() % 8) == 0 ? builder.length() / 8 : builder.length() / 8 + 1;
        endLen = builder.length() % 8;
        // 創(chuàng)建 存儲(chǔ)后的bytes壓縮數(shù)組
        byte[] huffmanCodeBytes = new byte[len];
        int index = 0;// 記錄是第幾個(gè)byte
        for (int i = 0; i < builder.length(); i += 8) {// 因?yàn)槭敲?位對應(yīng)一個(gè)byte,所以步長+8
            String strByte;
            // 兩種情況i+8超過最后位置和不超過的分別賦值

            strByte = i + 8 > builder.length() ? builder.substring(i) : builder.substring(i, i + 8);
            // 后面一個(gè)參數(shù)2表示轉(zhuǎn)換成二進(jìn)制
            huffmanCodeBytes[index++] = (byte) Integer.parseInt(strByte, 2);
        }
        return huffmanCodeBytes;
    }

處理之后的結(jié)果

處理前的字節(jié)數(shù)組:[97, 98, 98, 99, 99, 99, 100, 100, 100, 100, 101, 101, 101, 101, 101] 長度=15
處理后的字節(jié)數(shù)組:[77, -127, 85, -1, 1] 長度=5
當(dāng)重復(fù)字符較多時(shí)痢缎,壓縮效率還是相當(dāng)可觀的

對整個(gè)壓縮流程創(chuàng)建一個(gè)接口函數(shù)方便調(diào)用

    

    public static void main(String[] args) {
        // TODO Auto-generated method stub
        String content = "abbcccddddeeeee";
        byte[] contentBytes = content.getBytes();
        byte[] huffmanCodeBytes = huffmanZip(contentBytes);
  }

    // 創(chuàng)建一個(gè)接口函數(shù)封裝好實(shí)現(xiàn)的細(xì)節(jié)
    /**
     * 
     * @param bytes 原始字符串對應(yīng)的字節(jié)數(shù)組
     * @return 返回處理后的字節(jié)數(shù)組
     */
    public static byte[] huffmanZip(byte[] bytes) {
        System.out.println("處理前的字節(jié)數(shù)組:"+Arrays.toString(bytes)+" 長度="+bytes.length);
        List<Node> nodes = getNodes(bytes);
        // 創(chuàng)建哈夫曼樹
        Node huffmanTreeRoot = createHuffmanTree(nodes);
        // 對應(yīng)的哈夫曼編碼
        Map<Byte, String> huffmanCodes = getCodes(huffmanTreeRoot);
        // 根據(jù)生成的哈夫曼編碼胁勺,得到壓縮后的數(shù)組
        byte[] huffmanCodeBytes = zip(bytes, huffmanCodes);
        System.out.println("處理后的字節(jié)數(shù)組:"+Arrays.toString(huffmanCodeBytes)+" 長度="+huffmanCodeBytes.length);
        preOrder(huffmanTreeRoot);
        return huffmanCodeBytes;
    }

2、解壓算法實(shí)現(xiàn)部分

基本思路

  • 字節(jié)數(shù)組轉(zhuǎn)換成二進(jìn)制字符串
  • 逆向處理生成好的哈夫曼編碼表
  • 根據(jù)逆向生成的哈夫曼表查詢生成原來的字節(jié)數(shù)組

字節(jié)數(shù)組轉(zhuǎn)換成二進(jìn)制字符串

思路就是用flag來標(biāo)識是否時(shí)最后一位独旷,通過之前的endLen得到最后一位如果不滿八位就進(jìn)行特殊處理

代碼處理主要用二進(jìn)制的操作署穗,byte的取值范圍為-128-127,如果出現(xiàn)負(fù)數(shù)則要進(jìn)行補(bǔ)高位

    // 完成數(shù)據(jù)轉(zhuǎn)成對應(yīng)的二進(jìn)制字符串'10101000...'
    /**
     * 將一個(gè)byte 轉(zhuǎn)成一個(gè)二進(jìn)制的字符串
     * 
     * @param b    傳入的是一個(gè)字節(jié)b
     * @param flag 標(biāo)志是否為最后一個(gè)字節(jié)嵌洼,true表示不是最后一個(gè)字節(jié)案疲,false表示是最后一個(gè)字節(jié)
     * @return 是該b對應(yīng)對二進(jìn)制對字符串,(注意是按照補(bǔ)碼返回)
     */
    public static String byteToBitString(boolean flag, byte b) {
        // 使用一個(gè)變量保持b
        int temp = b; // 將b轉(zhuǎn)成int
        temp |= 256;
        String str = Integer.toBinaryString(temp);// 返回的是temp對應(yīng)的二進(jìn)制補(bǔ)碼
        if (flag || (flag == false && endLen == 0)) {
      //字符串的截取麻养,只拿后八位
            return str.substring(str.length() - 8);
        } else {
      //不滿8bit有多少位拿多少位
            return str.substring(str.length() - endLen);
        }

逆向處理生成好的哈夫曼編碼表

用循環(huán)把編碼表key(a,b,c)-value(0101) 倒轉(zhuǎn)過來

        // 把哈夫曼編碼表進(jìn)行調(diào)換褐啡,因?yàn)橐M(jìn)行反向查詢
        Map<String, Byte> map = new HashMap<String, Byte>();
        for (Map.Entry<Byte, String> entry : huffmanCodes.entrySet()) {
            map.put(entry.getValue(), entry.getKey());
        }

根據(jù)逆向生成的哈夫曼表查詢生成原來的字節(jié)數(shù)組

取生成好的010101二進(jìn)制字符串,根據(jù)哈夫曼編碼表查出原來的每個(gè)字節(jié)鳖昌,采用雙指針移動(dòng)的方式去hashmap中查詢备畦,查到了對應(yīng)的字節(jié),吧它加入到我們的list中许昨,再把list轉(zhuǎn)換成byte數(shù)組進(jìn)行返回

    // 編寫一個(gè)方法懂盐,完成對壓縮數(shù)據(jù)對解碼
    /**
     * @param huffmanCodes 哈夫曼編碼表
     * @param huffmanBytes 哈夫曼編碼得到對字節(jié)數(shù)組
     * @return 就是原來對字符串對應(yīng)對數(shù)組
     */
    public static byte[] decode(Map<Byte, String> huffmanCodes, byte[] huffmanBytes) {
        // 1.先得到 huffmanBytes 對應(yīng)對二進(jìn)制字符串,形式10101000...
        StringBuilder builder = new StringBuilder();
        // 2.將byte數(shù)組轉(zhuǎn)成二進(jìn)制的字符串
        for (int i = 0; i < huffmanBytes.length; i++) {
            byte b = huffmanBytes[i];
            // 判斷是不是最后一個(gè)字節(jié)
            boolean flag = (i == huffmanBytes.length - 1);
            builder.append(byteToBitString(!flag, b));
        }
        // 創(chuàng)建集合糕档,存放byte
        List<Byte> list = new ArrayList<>();
        for (int i = 0; i < builder.length();) {
            int count = 1; // 小的計(jì)數(shù)器
            boolean flag = true;
            Byte b = null;
            while (flag) {
                // 取出一個(gè)bit '1'或者'0'
                String key = builder.substring(i, i + count); // i 不動(dòng) 讓count移動(dòng)莉恼,直到匹配到一個(gè)字符
                b = map.get(key);
                if (b == null) {// 沒有匹配到
                    count++;
                } else {
                    flag = false;
                }
            }
            list.add(b);
            i = i + count;
        }
        // 當(dāng)for循環(huán)結(jié)束以后,list中存放了所有當(dāng)字符
        // 把list中的數(shù)據(jù)放入到byte[] 并返回
        byte b[] = new byte[list.size()];
        for (int i = 0; i < b.length; i++) {
            b[i] = list.get(i);
        }
        return b;
    }

所有調(diào)用過程main函數(shù)

    public static void main(String[] args) {
        // TODO Auto-generated method stub
        String content = "abbcccddddeeeee";
        byte[] contentBytes = content.getBytes();
        byte[] huffmanCodeBytes = huffmanZip(contentBytes);
        byte[] source = decode(huffmanCodes, huffmanCodeBytes);
        System.out.println("原來的字符串=" + new String(source));
    }

壓縮-解壓的處理結(jié)果

處理前的字節(jié)數(shù)組:[97, 98, 98, 99, 99, 99, 100, 100, 100, 100, 101, 101, 101, 101, 101] 長度=15
處理后的字節(jié)數(shù)組:[77, -127, 85, -1, 1] 長度=5
原來的字符串=abbcccddddeeeee

總結(jié)

以上就就是關(guān)于所有哈java實(shí)現(xiàn)夫曼編碼的所有細(xì)節(jié)速那,希望你對此有更清晰的的了解
完整源代碼移步github

聲明

1类垫、如果在文章當(dāng)中發(fā)現(xiàn)有描述錯(cuò)誤的地方,還請您不吝指出琅坡,萬分感謝!
2残家、此文章系本人原創(chuàng)作品榆俺,轉(zhuǎn)發(fā)請注明出處!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末坞淮,一起剝皮案震驚了整個(gè)濱河市茴晋,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌回窘,老刑警劉巖诺擅,帶你破解...
    沈念sama閱讀 217,734評論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異啡直,居然都是意外死亡烁涌,警方通過查閱死者的電腦和手機(jī)苍碟,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,931評論 3 394
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來撮执,“玉大人微峰,你說我怎么就攤上這事∈闱” “怎么了蜓肆?”我有些...
    開封第一講書人閱讀 164,133評論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長谋币。 經(jīng)常有香客問我仗扬,道長,這世上最難降的妖魔是什么蕾额? 我笑而不...
    開封第一講書人閱讀 58,532評論 1 293
  • 正文 為了忘掉前任早芭,我火速辦了婚禮,結(jié)果婚禮上凡简,老公的妹妹穿的比我還像新娘逼友。我一直安慰自己,他們只是感情好秤涩,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,585評論 6 392
  • 文/花漫 我一把揭開白布帜乞。 她就那樣靜靜地躺著,像睡著了一般筐眷。 火紅的嫁衣襯著肌膚如雪黎烈。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,462評論 1 302
  • 那天匀谣,我揣著相機(jī)與錄音照棋,去河邊找鬼。 笑死武翎,一個(gè)胖子當(dāng)著我的面吹牛烈炭,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播宝恶,決...
    沈念sama閱讀 40,262評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼符隙,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了垫毙?” 一聲冷哼從身側(cè)響起霹疫,我...
    開封第一講書人閱讀 39,153評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎综芥,沒想到半個(gè)月后丽蝎,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,587評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡膀藐,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,792評論 3 336
  • 正文 我和宋清朗相戀三年屠阻,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了红省。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,919評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡栏笆,死狀恐怖类腮,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情蛉加,我是刑警寧澤蚜枢,帶...
    沈念sama閱讀 35,635評論 5 345
  • 正文 年R本政府宣布,位于F島的核電站针饥,受9級特大地震影響厂抽,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜丁眼,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,237評論 3 329
  • 文/蒙蒙 一筷凤、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧苞七,春花似錦藐守、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,855評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至惠啄,卻和暖如春慎恒,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背撵渡。 一陣腳步聲響...
    開封第一講書人閱讀 32,983評論 1 269
  • 我被黑心中介騙來泰國打工融柬, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人趋距。 一個(gè)月前我還...
    沈念sama閱讀 48,048評論 3 370
  • 正文 我出身青樓粒氧,卻偏偏與公主長得像,于是被迫代替她去往敵國和親节腐。 傳聞我的和親對象是個(gè)殘疾皇子靠欢,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,864評論 2 354

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