HashMap源碼分析(JDK 1.8)

一救鲤、概述

HashMap是我們在編程中遇到極其頻繁忿檩、非常重要的一個集合類,如果能對HashMap做進一步的性能優(yōu)化是非常有價值的而JDK 1.8做到了介蛉,所以非常有必要學(xué)習(xí)HashMap的重點源碼萌庆,了解大師的手法。

二币旧、底層數(shù)據(jù)結(jié)構(gòu)

畫圖真的是個累活践险,好的畫圖工具很重要啊,上面這兩張圖分別畫出了JDK 1.7吹菱、1.8底層數(shù)據(jù)結(jié)構(gòu)捏境,在JDK 1.7、1.8中都使用
了散列算法毁葱,但是在JDK 1.8中引入了紅黑樹,在鏈表的長度大于等于8并且hash桶的長度大于等于64的時候贰剥,會將鏈表進行樹化倾剿。這里的樹使用的數(shù)據(jù)結(jié)構(gòu)是紅黑樹,紅黑樹是一個自平衡的二叉查找樹蚌成,查找效率會從鏈表的o(n)降低為o(logn)前痘,效率是非常大的提高。

那為什么不將鏈表全部換成二叉樹呢担忧?這里主要有兩個方面芹缔。

  • 第一個是鏈表的結(jié)構(gòu)比紅黑樹簡單,構(gòu)造紅黑樹要比構(gòu)造鏈表復(fù)雜瓶盛,所以在鏈表的節(jié)點不多的情況下最欠,從整體的性能看來,
    數(shù)組+鏈表+紅黑樹的結(jié)構(gòu)不一定比數(shù)組+鏈表的結(jié)構(gòu)性能高惩猫。

  • 第二個是HashMap頻繁的resize(擴容)芝硬,擴容的時候需要重新計算節(jié)點的索引位置,也就是會將紅黑樹進行拆分和重組其實
    這是很復(fù)雜的轧房,這里涉及到紅黑樹的著色和旋轉(zhuǎn)拌阴,有興趣的可以看看紅黑樹的原理,這又是一個比鏈表結(jié)構(gòu)耗時的操作奶镶,所以為鏈表樹化設(shè)置一個閥值是非常有必要的迟赃。

三陪拘、源碼分析

3.1 類結(jié)構(gòu)

  • 下圖是HashMap的類結(jié)構(gòu),大家看看有個概念

3.2 類注釋

我建議大家在讀源碼時可以先看看類注釋纤壁,往往類注釋會給我們一些重要的信息左刽,這里LZ給大家總結(jié)一下。

(1)允許NULL值摄乒,NULL鍵

(2)不要輕易改變負載因子悠反,負載因子過高會導(dǎo)致鏈表過長,查找鍵值對時間復(fù)雜度就會增高馍佑,負載因子過低會導(dǎo)致hash桶的 數(shù)量過多斋否,空間復(fù)雜度會增高

(3)Hash表每次會擴容長度為以前的2倍

(4)HashMap是多線程不安全的,我在JDK1.7進行多線程put操作拭荤,之后遍歷茵臭,直接死循環(huán),CPU飆到100%舅世,在JDK 1.8中進行多線程操作會出現(xiàn)節(jié)點和value值丟失旦委,為什么JDK1.7與JDK1.8多線程操作會出現(xiàn)很大不同,是因為JDK 1.8的作者對resize方法進行了優(yōu)化不會產(chǎn)生鏈表閉環(huán)雏亚。這也是本章的重點之一缨硝,具體的細節(jié)大家可以去查閱資料。這里我就不解釋太多了

(5)盡量設(shè)置HashMap的初始容量罢低,尤其在數(shù)據(jù)量大的時候查辩,防止多次resize

3.3 類常量

  //默認hash桶初始長度16
  static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; 

  //hash表最大容量2的30次冪
  static final int MAXIMUM_CAPACITY = 1 << 30;

  //默認負載因子 0.75
  static final float DEFAULT_LOAD_FACTOR = 0.75f;

  //鏈表的數(shù)量大于等于8個并且桶的數(shù)量大于等于64時鏈表樹化 
  static final int TREEIFY_THRESHOLD = 8;

  //hash表某個節(jié)點鏈表的數(shù)量小于等于6時樹拆分
  static final int UNTREEIFY_THRESHOLD = 6;

  //樹化時最小桶的數(shù)量
  static final int MIN_TREEIFY_CAPACITY = 64;

3.4 實例變量

  //hash桶
  transient Node<K,V>[] table;                         

  //鍵值對的數(shù)量
  transient int size;

  //HashMap結(jié)構(gòu)修改的次數(shù)
  transient int modCount;

  //擴容的閥值,當鍵值對的數(shù)量超過這個閥值會產(chǎn)生擴容
  int threshold;

  //負載因子
  final float loadFactor;

3.5 構(gòu)造函數(shù)

public HashMap(int initialCapacity, float loadFactor) {                                                                   
        if (initialCapacity < 0)
            throw new IllegalArgumentException("Illegal initial capacity: " +
                                               initialCapacity);
        if (initialCapacity > MAXIMUM_CAPACITY)
            initialCapacity = MAXIMUM_CAPACITY;
        if (loadFactor <= 0 || Float.isNaN(loadFactor))
            throw new IllegalArgumentException("Illegal load factor: " + loadFactor);
        this.loadFactor = loadFactor;
        //下面介紹一下這行代碼的作用
        this.threshold = tableSizeFor(initialCapacity);
    }

    public HashMap(int initialCapacity) {
        this(initialCapacity, DEFAULT_LOAD_FACTOR);
    }

    public HashMap() {
        this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
    }

    public HashMap(Map<? extends K, ? extends V> m) {
        this.loadFactor = DEFAULT_LOAD_FACTOR;
        putMapEntries(m, false);
    }

HashMap有4個構(gòu)造函數(shù)网持。

hash桶沒有在構(gòu)造函數(shù)中初始化宜岛,而是在第一次存儲鍵值對的時候進行初始化。 這里重點看下
tableSizeFor(initialCapacity)方法功舀,這個方法的作用是萍倡,將你傳入的initialCapacity做計算,返回一個大于等于initialCapacity
最小的2的冪次方辟汰。

所以這個操作保證無論你傳入的初始化Hash桶長度參數(shù)是多少列敲,最后hash表初始化的長度都是2的冪次方。比如你輸入的是6帖汞,計算出來結(jié)果就是8酿炸。

下面貼出源碼。

static final int tableSizeFor(int cap) {                                                                      
        int n = cap - 1;
        n |= n >>> 1;
        n |= n >>> 2;
        n |= n >>> 4;
        n |= n >>> 8;
        n |= n >>> 16;
        return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
    }

3.6 插入

public V put(K key, V value) {
    return putVal(hash(key), key, value, false, true);
}
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,                                     
               boolean evict) {
    Node<K,V>[] tab; Node<K,V> p; int n, i;
    //當table為空時涨冀,這里初始化table填硕,不是通過構(gòu)造函數(shù)初始化,而是在插入時通過擴容初始化,有效防止了初始化HashMap沒有數(shù)據(jù)插入造成空間浪費可能造成內(nèi)存泄露的情況
    if ((tab = table) == null || (n = tab.length) == 0)
        n = (tab = resize()).length;
    //存放新鍵值對
    if ((p = tab[i = (n - 1) & hash]) == null)
        tab[i] = newNode(hash, key, value, null);
    else {
        Node<K,V> e; K k;
        //舊鍵值對的覆蓋
        if (p.hash == hash &&
            ((k = p.key) == key || (key != null && key.equals(k))))
            e = p;
        //在紅黑樹中查找舊鍵值對更新
        else if (p instanceof TreeNode)
            e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
        else {
            //將新鍵值對放在鏈表的最后
            for (int binCount = 0; ; ++binCount) {
                if ((e = p.next) == null) {
                    p.next = newNode(hash, key, value, null);
                    //當鏈表的長度大于等于樹化閥值扁眯,并且hash桶的長度大于等于MIN_TREEIFY_CAPACITY壮莹,鏈表轉(zhuǎn)化為紅黑樹
                    if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                        treeifyBin(tab, hash);
                    break;
                }
                //鏈表中包含鍵值對
                if (e.hash == hash &&
                    ((k = e.key) == key || (key != null && key.equals(k))))
                    break;
                p = e;
            }
        }
        //map中含有舊key,返回舊值
        if (e != null) { 
            V oldValue = e.value;
            if (!onlyIfAbsent || oldValue == null)
                e.value = value;
            afterNodeAccess(e);
            return oldValue;
        }
    }
    //map調(diào)整次數(shù)加1
    ++modCount;
    //鍵值對的數(shù)量達到閾值需要擴容
    if (++size > threshold)
        resize();
    afterNodeInsertion(evict);
    return null;
}

HashMap插入跟我們平時使用時的感覺差不多姻檀,下面總結(jié)一下命满。

(1)插入的鍵值對是新鍵值對,如果hash表沒有初始化會進行初始化绣版,否則將鍵值對插入鏈表尾部胶台,可能需要鏈表樹化和
擴容

(2)插入的鍵值對中的key已經(jīng)存在,更新鍵值對在put的方法里我們注意看下hash(key)方法杂抽,這是計算鍵值對hash值的方法诈唬,下面給出源碼

static final int hash(Object key) {                                                                          
        int h;
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
    }

hashCode()是一個int類型的本地方法,也就將key的hashCode無符號右移16位然后與hashCode異或從而得到hash值在putVal方法中(n - 1)& hash計算得到桶的索引位置 缩麸,那么現(xiàn)在有兩個疑問铸磅,為什么要計算hash值?為什么不用 hash % n?

  • 為什么要計算hash值杭朱,而不用hashCode阅仔,用為通常n是很小的,而hashCode是32位弧械,如果(n - 1)& hashCode那么當n大于2的16次方加1八酒,也就是65537后(n - 1)的高位數(shù)據(jù)才能與hashCode的高位數(shù)據(jù)相與,當n很小是只能使用上hashCode低
    16位的數(shù)據(jù)刃唐,這會產(chǎn)生一個問題丘跌,既鍵值對在hash桶中分布不均勻,導(dǎo)致鏈表過長唁桩,而把hashCode>>>16無符號右移16位讓
    高16位間接的與(n - 1)參加計算,從而讓鍵值對分布均勻耸棒。降低hash碰撞荒澡。

  • 為什么使用(n - 1)& hash 而不使用hash% n呢?其實這兩種結(jié)果是等價的与殃,但是&的效率比%高单山,原因因為&運算是二
    進制直接運算,而計算機天生就認得二進制幅疼。下面畫圖說明一下

上圖 hash&(n - 1)的結(jié)果是2米奸,而其實hash%n 的結(jié)果也是2, hash&(n - 1)與hash%n的結(jié)果是等價的。

3.7 擴容

final Node<K,V>[] resize() {
        Node<K,V>[] oldTab = table;
        int oldCap = (oldTab == null) ? 0 : oldTab.length;
        int oldThr = threshold;
        int newCap, newThr = 0;
        //如果舊hash桶不為空
        if (oldCap > 0) {
            //超過hash桶的最大長度爽篷,將閥值設(shè)為最大值
            if (oldCap >= MAXIMUM_CAPACITY) {
                threshold = Integer.MAX_VALUE;
                return oldTab;
            }
            //新的hash桶的長度2被擴容沒有超過最大長度悴晰,將新容量閥值擴容為以前的2倍
            else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
                     oldCap >= DEFAULT_INITIAL_CAPACITY)
                newThr = oldThr << 1; // double threshold
        }
        //如果hash表閾值已經(jīng)初始化過
        else if (oldThr > 0) // initial capacity was placed in threshold
            newCap = oldThr;
        //如果舊hash桶,并且hash桶容量閾值沒有初始化,那么需要初始化新的hash桶的容量和新容量閥值
        else {              
            newCap = DEFAULT_INITIAL_CAPACITY;
            newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
        }
        //新的局部變量閥值賦值
        if (newThr == 0) {
            float ft = (float)newCap * loadFactor;
            newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
                      (int)ft : Integer.MAX_VALUE);
        }
        //為當前容量閥值賦值
        threshold = newThr;
        @SuppressWarnings({"rawtypes","unchecked"})
            //初始化hash桶
            Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
        table = newTab;
        //如果舊的hash桶不為空铡溪,需要將舊的hash表里的鍵值對重新映射到新的hash桶中
        if (oldTab != null) {
            for (int j = 0; j < oldCap; ++j) {
                Node<K,V> e;
                if ((e = oldTab[j]) != null) {
                    oldTab[j] = null;
                    //只有一個節(jié)點漂辐,通過索引位置直接映射
                    if (e.next == null)
                        newTab[e.hash & (newCap - 1)] = e;
                    //如果是紅黑樹,需要進行樹拆分然后映射
                    else if (e instanceof TreeNode)
                        ((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
                    else { 
                    //如果是多個節(jié)點的鏈表棕硫,將原鏈表拆分為兩個鏈表髓涯,兩個鏈表的索引位置,一個為原索引哈扮,一個為原索引加上舊Hash桶長度的偏移量       
                        Node<K,V> loHead = null, loTail = null;
                        Node<K,V> hiHead = null, hiTail = null;
                        Node<K,V> next;
                        do {
                            next = e.next;
                            //鏈表1
                            if ((e.hash & oldCap) == 0) {
                                if (loTail == null)
                                    loHead = e;
                                else
                                    loTail.next = e;
                                loTail = e;
                            }
                            //鏈表2
                            else {
                                if (hiTail == null)
                                    hiHead = e;
                                else
                                    hiTail.next = e;
                                hiTail = e;
                            }
                        } while ((e = next) != null);
                        //鏈表1存于原索引
                        if (loTail != null) {
                            loTail.next = null;
                            newTab[j] = loHead;
                        }
                        //鏈表2存于原索引加上原h(huán)ash桶長度的偏移量
                        if (hiTail != null) {
                            hiTail.next = null;
                            newTab[j + oldCap] = hiHead;
                        }
                    }
                }
            }
        }
        return newTab;
    }

那么什么時候回產(chǎn)生擴容呢纬纪?

(1)初始化HashMap時,第一次進行put操作

(2)當鍵值對的個數(shù)大于threshold閥值時產(chǎn)生擴容滑肉,threshold=size*loadFactor

上面就是HashMap擴容的源代碼包各,我已經(jīng)加上了注釋,相信大家都能看懂了赦邻∷杵澹總結(jié)一下,HaspMap擴容就是就是先計算
新的hash表容量和新的容量閥值惶洲,然后初始化一個新的hash表按声,將舊的鍵值對重新映射在新的hash表里。這里實現(xiàn)的細節(jié)當然
沒有我說的那么簡單恬吕,如果在舊的hash表里涉及到紅黑樹签则,那么在映射到新的hash表中還涉及到紅黑樹的拆分。

在擴容的源代碼中作者有一個使用很巧妙的地方铐料,是鍵值對分布更均勻渐裂,不知道讀者是否有看出來。在遍歷原h(huán)ash桶時的
一個鏈表時钠惩,因為擴容后長度為原h(huán)ash表的2倍柒凉,假設(shè)把擴容后的hash表分為兩半,分為低位和高位篓跛,如果能把原鏈表的鍵值對膝捞,
一半放在低位,一半放在高位愧沟,這樣的索引效率是最高的蔬咬。那看看源碼里是怎樣寫的。大師通過e.hash & oldCap == 0來判斷沐寺,
這和e.hash & (oldCap - 1) 有什么區(qū)別呢林艘。下面我通過畫圖來解釋一下。

1.jpg

因為n是2的整次冪混坞,二進制表示除了最高位為1外狐援,其他低位全為0,那么e.hash & oldCap 是否等于0,取決于n對應(yīng)最高位
相對于e.hash那一位是0還是1,比如說n = 16咕村,二進制為10000场钉,第5位為1,e.hash & oldCap 是否等于0就取決于e.hash第5
位是0還是1懈涛,這就相當于有50%的概率放在新hash表低位逛万,50%的概率放在新hash表高位。大家應(yīng)該明白了e.hash & oldCap
== 0的好處與作用了吧批钠。

其實宇植,到這里基本上HashMap的核心內(nèi)容都講完了,相信大家對HashMap的源碼有一定了解了埋心。在源碼中還有鍵值對的查詢和刪除都比較簡單指郁,這里就不在過多贅述了,對于紅黑樹的構(gòu)造拷呆、旋轉(zhuǎn)闲坎、著色,我覺得大家有興趣可以了解一下茬斧,畢竟我們不
是HashMap的開發(fā)者腰懂,不用了解過多的細節(jié),鉆墻角项秉。知道大致的原理即可绣溜。

3.8 清除

本來到這里就要結(jié)束了,但是LZ還是想跟大家聊一下HashMap總的clear()方法娄蔼,下面貼出源碼怖喻。

public void clear() {
        Node<K,V>[] tab;
        modCount++;
        if ((tab = table) != null && size > 0) {
            size = 0;
            for (int i = 0; i < tab.length; ++i)
                tab[i] = null;
        }
    }

HashMap其實這段代碼特別簡單,為什么貼出來呢岁诉,是因為我在看過別的博客里產(chǎn)生過疑問锚沸,到底是clear好還是新建一
個HashMap好。我認為clear()比新建一個HashMap好涕癣。下面從空間復(fù)雜度和時間復(fù)雜度來解釋一下哗蜈。

從時間角度來看,這個循環(huán)是非常簡單無復(fù)雜邏輯属划,并不十分耗資源。而新建一個HashMap候生,首先他在在堆內(nèi)存中年輕代中查看是否有足夠空間能夠存儲同眯,如果能夠存儲,那么創(chuàng)建順利完成唯鸭,但如果HashMap非常大须蜗,年輕代很難有足夠的空間存儲,如果老年代中有足夠空間存儲這個HashMap,那么jvm會將HashMap直接存儲在老年代中明肮,如果老年代中空間不夠菱农,這時候會觸發(fā)一次minor gc,會產(chǎn)生小規(guī)模的gc停頓柿估,如果發(fā)生minor gc之后仍不能存儲HashMap循未,那么會發(fā)生整個堆的gc,也就是
full gc秫舌,這個gc停頓是很恐怖的的妖。實際上的gc順序就是這樣的,并且可能發(fā)生多次minor gc和full gc,如果發(fā)現(xiàn)年輕代和老年代
均不能存儲HashMap足陨,那么就會觸發(fā)OOM嫂粟,而clear()是肯定不會觸發(fā)OOM的,所以數(shù)據(jù)里特別大的情況下墨缘,千萬不要創(chuàng)建一
個新的HashMap代替clear()方法星虹。

從空間角度看,原HashMap雖然不用镊讼,如果數(shù)據(jù)未被清空宽涌,是不可能被jvm回收的,因為HashMap是強引用類型的狠毯,從而造成內(nèi)存泄漏护糖。所以綜上所述我
是不建議新建一個HashMap代替clear()的,并且很多源碼中clear()方法很常用嚼松,這就是最好的證明嫡良。

四、總結(jié)

(1)HashMap允許NULL值献酗,NULL鍵

(2)不要輕易改變負載因子寝受,負載因子過高會導(dǎo)致鏈表過長,查找鍵值對時間復(fù)雜度就會增高罕偎,負載因子過低會導(dǎo)致hash桶的數(shù)量過多很澄,空間復(fù)雜度會增高

(3)Hash表每次會擴容長度為以前的2倍

(4)HashMap是多線程不安全的,我在JDK 1.7進行多線程put操作颜及,之后遍歷甩苛,直接死循環(huán),CPU飆到100%俏站,在JDK 1.8中
進行多線程操作會出現(xiàn)節(jié)點和value值丟失讯蒲,為什么JDK1.7與JDK1.8多線程操作會出現(xiàn)很大不同,是因為JDK 1.8的作者對resize
方法進行了優(yōu)化不會產(chǎn)生鏈表閉環(huán)肄扎。這也是本章的重點之一墨林,具體的細節(jié)大家可以去查閱資料赁酝。這里我就不解釋太多了

(5)盡量設(shè)置HashMap的初始容量,尤其在數(shù)據(jù)量大的時候旭等,防止多次resize

(6)HashMap在JDK 1.8在做了很好性能的提升酌呆,我看到過在JDK1.7和JDK1.8get操作性能對比JDK1.8是要優(yōu)于JDK 1.7的,
大家感興趣的可以自己做個測試搔耕,所以還沒有升級到JDK1.8的小伙伴趕緊的吧隙袁。

總結(jié)就把類注釋的給搬過來了,其實在本篇文章中有一個知識點沒有詳細分析度迂,就是HashMap在多線程不安全的原因藤乙,尤其擴
容在JDK 1.7 會產(chǎn)生鏈表閉環(huán),因為要畫很多圖惭墓,我還沒找到合適的工具坛梁,后期補充吧。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末腊凶,一起剝皮案震驚了整個濱河市划咐,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌钧萍,老刑警劉巖褐缠,帶你破解...
    沈念sama閱讀 217,657評論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異风瘦,居然都是意外死亡队魏,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,889評論 3 394
  • 文/潘曉璐 我一進店門万搔,熙熙樓的掌柜王于貴愁眉苦臉地迎上來胡桨,“玉大人,你說我怎么就攤上這事瞬雹∶烈辏” “怎么了?”我有些...
    開封第一講書人閱讀 164,057評論 0 354
  • 文/不壞的土叔 我叫張陵酗捌,是天一觀的道長呢诬。 經(jīng)常有香客問我,道長胖缤,這世上最難降的妖魔是什么尚镰? 我笑而不...
    開封第一講書人閱讀 58,509評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮哪廓,結(jié)果婚禮上狗唉,老公的妹妹穿的比我還像新娘。我一直安慰自己撩独,他們只是感情好敞曹,可當我...
    茶點故事閱讀 67,562評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著综膀,像睡著了一般澳迫。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上剧劝,一...
    開封第一講書人閱讀 51,443評論 1 302
  • 那天橄登,我揣著相機與錄音,去河邊找鬼讥此。 笑死拢锹,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的萄喳。 我是一名探鬼主播卒稳,決...
    沈念sama閱讀 40,251評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼他巨!你這毒婦竟也來了充坑?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,129評論 0 276
  • 序言:老撾萬榮一對情侶失蹤染突,失蹤者是張志新(化名)和其女友劉穎捻爷,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體份企,經(jīng)...
    沈念sama閱讀 45,561評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡也榄,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,779評論 3 335
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了司志。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片甜紫。...
    茶點故事閱讀 39,902評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖俐芯,靈堂內(nèi)的尸體忽然破棺而出棵介,到底是詐尸還是另有隱情,我是刑警寧澤吧史,帶...
    沈念sama閱讀 35,621評論 5 345
  • 正文 年R本政府宣布邮辽,位于F島的核電站,受9級特大地震影響贸营,放射性物質(zhì)發(fā)生泄漏吨述。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,220評論 3 328
  • 文/蒙蒙 一钞脂、第九天 我趴在偏房一處隱蔽的房頂上張望揣云。 院中可真熱鬧,春花似錦冰啃、人聲如沸邓夕。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,838評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽焚刚。三九已至点弯,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間矿咕,已是汗流浹背抢肛。 一陣腳步聲響...
    開封第一講書人閱讀 32,971評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留碳柱,地道東北人捡絮。 一個月前我還...
    沈念sama閱讀 48,025評論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像莲镣,于是被迫代替她去往敵國和親福稳。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,843評論 2 354