面試?yán)媳粏朒ashMap涎永,今天基于jdk1.8帶你搞懂HashMap的底層實現(xiàn)原理

前言

HashMap是Java后端工程師面試的必問題涵叮,因為其中的知識點太多,很適合用來考察面試者的Java基礎(chǔ)。今天基于jdk1.8來研究一下HashMap的底層實現(xiàn)。

HashMap的內(nèi)部數(shù)據(jù)結(jié)構(gòu)

JDK1.7是數(shù)組+鏈表
JDK1.8是數(shù)組+鏈表+紅黑樹

HashMap在jdk8中相較于jdk7在底層實現(xiàn)方面的不同:

  1. new HashMap();底層沒創(chuàng)建一個長度為16的數(shù)組
  2. jdk 8底層的數(shù)組是:Node[],而非Entry[]
  3. 首次調(diào)用put()方法時贝润,底層創(chuàng)建長度為16的數(shù)組
  4. jdk7底層結(jié)構(gòu)只:數(shù)組+鏈表。jdk8中底層結(jié)構(gòu):數(shù)組+鏈表+紅黑樹铝宵。
  5. 形成鏈表時打掘,七上八下(jdk7:新的元素指向舊的元素,頭插。jdk8:舊的元素指向新的元素鹏秋,尾插)
  6. 當(dāng)數(shù)組的某一個索引位置上的元素以鏈表形式存在的數(shù)據(jù)個數(shù) > 8 且當(dāng)前數(shù)組的長度 > 64時尊蚁,此時此索引位置上的所數(shù)據(jù)改為使用紅黑樹存儲。

數(shù)據(jù)結(jié)構(gòu)圖

hashMap中幾個重要的字段(JDK1.8)

//默認(rèn)初始容量為16侣夷,0000 0001 右移4位 0001 0000為16横朋,主干數(shù)組的初始容量為16,而且這個數(shù)組
//必須是2的倍數(shù)(后面說為什么是2的倍數(shù))
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16

//最大容量為int的最大值除2
static final int MAXIMUM_CAPACITY = 1 << 30;

//默認(rèn)加載因子為0.75
static final float DEFAULT_LOAD_FACTOR = 0.75f;

//閾值百拓,如果主干數(shù)組上的鏈表的長度大于8琴锭,鏈表轉(zhuǎn)化為紅黑樹
 static final int TREEIFY_THRESHOLD = 8;

//hash表擴(kuò)容后晰甚,如果發(fā)現(xiàn)某一個紅黑樹的長度小于6,則會重新退化為鏈表
 static final int UNTREEIFY_THRESHOLD = 6;

//當(dāng)hashmap容量大于64時决帖,鏈表才能轉(zhuǎn)成紅黑樹
 static final int MIN_TREEIFY_CAPACITY = 64;

//臨界值=主干數(shù)組容量*負(fù)載因子
int threshold;

hashMap的構(gòu)造方法:

//initialCapacity為初始容量厕九,loadFactor為負(fù)載因子
public HashMap(int initialCapacity, float loadFactor) {
        //初始容量小于0,拋出非法數(shù)據(jù)異常
        if (initialCapacity < 0)
            throw new IllegalArgumentException("Illegal initial capacity: " +
                                               initialCapacity);
        //初始容量最大為MAXIMUM_CAPACITY
        if (initialCapacity > MAXIMUM_CAPACITY)
            initialCapacity = MAXIMUM_CAPACITY;
        //負(fù)載因子必須大于0地回,并且是合法數(shù)字
        if (loadFactor <= 0 || Float.isNaN(loadFactor))
            throw new IllegalArgumentException("Illegal load factor: " +
                                               loadFactor);

        this.loadFactor = loadFactor;
        //將初始容量轉(zhuǎn)成2次冪
        this.threshold = tableSizeFor(initialCapacity);
    }

    //tableSizeFor的作用就是扁远,如果傳入A,當(dāng)A大于0刻像,小于定義的最大容量時畅买,
  //  如果A是2次冪則返回A,否則將A轉(zhuǎn)化為一個比A大且差距最小的2次冪细睡。  
    //例如傳入7返回8皮获,傳入8返回8,傳入9返回16
  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;
    }

    //調(diào)用上面的構(gòu)造方法纹冤,自定義初始容量,負(fù)載因子為默認(rèn)的0.75
 public HashMap(int initialCapacity) {
        this(initialCapacity, DEFAULT_LOAD_FACTOR);
    }

    //默認(rèn)構(gòu)造方法购公,負(fù)載因子為0.75萌京,初始容量為DEFAULT_INITIAL_CAPACITY=16,初始容量在第一次put時才會初始化
 public HashMap() {
        this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
    }

    //傳入一個MAP集合的構(gòu)造方法
 public HashMap(Map<? extends K, ? extends V> m) {
        this.loadFactor = DEFAULT_LOAD_FACTOR;
        putMapEntries(m, false);
    }

HashMap的數(shù)據(jù)插入原理

  1. 判斷數(shù)組是否為空宏浩,為空進(jìn)行初始化,在實例化以后知残,底層創(chuàng)建了長度是16的一維數(shù)組Entry[] table。
  2. 數(shù)組不為空比庄,首先求妹,調(diào)用key所在類的hashCode()計算key1哈希值,此哈希值經(jīng)過(n - 1) & hash計算以后佳窑,得到在Entry數(shù)組中的存放位置制恍。
  3. 判斷table[index] 此位置上是否存在數(shù)據(jù),沒有數(shù)據(jù)就構(gòu)造一個Node節(jié)點存放在 table[index] 中
  4. 如果此位置上存在數(shù)據(jù)神凑,發(fā)生了hash沖突(意味著此位置上存在一個或多個數(shù)據(jù)(以鏈表形式存在))
    ? ? 4.1 比較key1和已存在數(shù)據(jù)的哈希值净神,如果key1的哈希值與已經(jīng)存在的數(shù)據(jù)的哈希值都不相同,此時key1-value1添加成功溉委。
    ? ? 4.2 如果key1的哈希值和已經(jīng)存在的某一個數(shù)據(jù)(key2-value2)的哈希值相同
    ? ? 4.3 繼續(xù)調(diào)用key1所在類的equals(key2)方法比較:
    ? ? ? ? ①如果equals()返回false:此時key1-value1添加成功鹃唯。
    ? ? ? ? ②如果equals()返回true: 使用value1替換value2
  5. 判斷首節(jié)點是不是紅黑樹節(jié)點
    ? ? 5.1 如果首節(jié)點是紅黑樹節(jié)點(TreeNode),將鍵值對添加到紅黑樹瓣喊。
    ? ? 5.2 如果首節(jié)點是鏈表坡慌,進(jìn)行遍歷尋找元素,有就覆蓋無則新建藻三,將鍵值對添加到鏈表洪橘。添加之后會判斷鏈表長度是否長度大于 8并且數(shù)組長度大于64跪者,大于的話鏈表轉(zhuǎn)換為紅黑樹。

插入完成之后判斷當(dāng)前節(jié)點數(shù)是否大于閾值梨树,如果大于開始擴(kuò)容為原數(shù)組的二倍坑夯。

put源碼分析


 public V put(K key, V value) {
        /**
         * 將key傳入hash方法,計算其對應(yīng)的hash值:
         *
         * 此處如果傳入的int類型的值:
         * ①向一個Object類型賦值一個int的值時抡四,會將int值自動封箱為Integer柜蜈。
         * ②integer類型的hashcode都是他自身的值,即h=key指巡;h >>> 16為無符號右移16位淑履,低位擠走,高位補(bǔ)0藻雪;^ 為按位異或,
         * 即轉(zhuǎn)成二進(jìn)制后秘噪,相異為1,相同為0勉耀,由此可發(fā)現(xiàn)指煎,當(dāng)傳入的值小于  2的16次方-1 時,調(diào)用這個方法返回的值便斥,都是自身的值至壤。
         */

        return putVal(hash(key), key, value, false, true);
    }

//onlyIfAbsent是true的話,不要改變現(xiàn)有的值
//evict為true的話枢纠,表處于創(chuàng)建模式 
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為空像街,長度為0,調(diào)用resize方法晋渺,調(diào)整table的長度(resize方法在下圖中)
        if ((tab = table) == null || (n = tab.length) == 0)
            /* 這里調(diào)用resize镰绎,其實就是第一次put時,對數(shù)組進(jìn)行初始化木西。
               如果是默認(rèn)構(gòu)造方法會執(zhí)行resize中的這幾句話:
               newCap = DEFAULT_INITIAL_CAPACITY;  新的容量等于默認(rèn)值16
               newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);            
               threshold = newThr;   臨界值等于16*0.75
               Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap]; 
               table = newTab; 將新的node數(shù)組賦值給table畴栖,然后return newTab

                如果是自定義的構(gòu)造方法則會執(zhí)行resize中的: 
                int oldThr = threshold;   
                newCap = oldThr;   新的容量等于threshold,這里的threshold都是2的倍數(shù)户魏,原因在    
                于傳入的數(shù)都經(jīng)過tableSizeFor方法驶臊,返回了一個新值,上面解釋過
                float ft = (float)newCap * loadFactor; 
                newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
                (int)ft : Integer.MAX_VALUE); 
                 threshold = newThr; 新的臨界值等于 (int)(新的容量*負(fù)載因子)
                Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
                table = newTab; return newTab;
            */
            n = (tab = resize()).length;  //將調(diào)用resize后構(gòu)造的數(shù)組的長度賦值給n
        if ((p = tab[i = (n - 1) & hash]) == null) //將數(shù)組長度與計算得到的hash值比較
            tab[i] = newNode(hash, key, value, null);//位置為空叼丑,將i位置上賦值一個node對象
        else {  //位置不為空
            Node<K,V> e; K k;
            if (p.hash == hash &&  // 如果這個位置的old節(jié)點與new節(jié)點的key完全相同
                ((k = p.key) == key || (key != null && key.equals(k)))) 
                e = p;             // 則e=p
            else if (p instanceof TreeNode) // 如果p已經(jīng)是樹節(jié)點的一個實例关翎,既這里已經(jīng)是樹了
                e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
            else {  //p與新節(jié)點既不完全相同,p也不是treenode的實例
                for (int binCount = 0; ; ++binCount) {  //一個死循環(huán)
                    if ((e = p.next) == null) {   //e=p.next,如果p的next指向為null
                        p.next = newNode(hash, key, value, null);  //指向一個新的節(jié)點
                        if (binCount >= TREEIFY_THRESHOLD - 1) // 如果鏈表長度大于等于8
                            treeifyBin(tab, hash);  //將鏈表轉(zhuǎn)為紅黑樹
                        break;
                    }
       if (e.hash == hash &&  //如果遍歷過程中鏈表中的元素與新添加的元素完全相同鸠信,則跳出循環(huán)
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        break;
                    p = e; //將p中的next賦值給p,即將鏈表中的下一個node賦值給p纵寝,
                           //繼續(xù)循環(huán)遍歷鏈表中的元素
                }
            }
            if (e != null) { //這個判斷中代碼作用為:如果添加的元素產(chǎn)生了hash沖突,那么調(diào)用                
                             //put方法時,會將他在鏈表中他的上一個元素的值返回
                V oldValue = e.value;
                if (!onlyIfAbsent || oldValue == null)  //判斷條件成立的話爽茴,將oldvalue替換        
                //為newvalue葬凳,返回oldvalue;不成立則不替換室奏,然后返回oldvalue
                    e.value = value;
                afterNodeAccess(e);  //這個方法在后面說
                return oldValue;
            }
        }
        ++modCount;  //記錄修改次數(shù)
        if (++size > threshold)   //如果元素數(shù)量大于臨界值火焰,則進(jìn)行擴(kuò)容
            resize();   //下面說
        afterNodeInsertion(evict);  
        return null;
    }

resize的源碼詳解,擴(kuò)容機(jī)制胧沫,單元素如何散列到新的數(shù)組中昌简,鏈表中的元素如何散列到新的數(shù)組中,紅黑樹中的元素如何散列到新的數(shù)組中绒怨?


//默認(rèn)構(gòu)造方法與自定義構(gòu)造方法第一次執(zhí)行resize的過程纯赎,這里再說一下擴(kuò)容的過程   
 final Node<K,V>[] resize() {
        Node<K,V>[] oldTab = table;
        int oldCap = (oldTab == null) ? 0 : oldTab.length;
        int oldThr = threshold;
        int newCap, newThr = 0;
        if (oldCap > 0) {  //擴(kuò)容肯定執(zhí)行這個分支
            if (oldCap >= MAXIMUM_CAPACITY) {   //當(dāng)容量超過最大值時,臨界值設(shè)置為int最大值
                threshold = Integer.MAX_VALUE;
                return oldTab;
            }
            else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
                     oldCap >= DEFAULT_INITIAL_CAPACITY) //擴(kuò)容容量為2倍南蹂,臨界值為2倍
                newThr = oldThr << 1;
        }
        else if (oldThr > 0) // 不執(zhí)行
            newCap = oldThr;
        else {                // 不執(zhí)行
            newCap = DEFAULT_INITIAL_CAPACITY;
            newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
        }
        if (newThr == 0) {  // 不執(zhí)行
            float ft = (float)newCap * loadFactor;
            newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
                      (int)ft : Integer.MAX_VALUE);
        }
        threshold = newThr;    //將新的臨界值賦值賦值給threshold
        @SuppressWarnings({"rawtypes","unchecked"})
            Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
        table = newTab;   //新的數(shù)組賦值給table

        //擴(kuò)容后犬金,重新計算元素新的位置
        if (oldTab != null) {   //原數(shù)組
            for (int j = 0; j < oldCap; ++j) {   //通過原容量遍歷原數(shù)組
                Node<K,V> e;
                if ((e = oldTab[j]) != null) {   //判斷node是否為空,將j位置上的節(jié)點
                //保存到e,然后將oldTab置為空六剥,這里為什么要把他置為空呢晚顷,置為空有什么好處嗎?疗疟?
                //難道是吧oldTab變?yōu)橐粋€空數(shù)組音同,便于垃圾回收?秃嗜? 這里不是很清楚
                    oldTab[j] = null;
                    if (e.next == null)          //判斷node上是否有鏈表
                        newTab[e.hash & (newCap - 1)] = e; //無鏈表,確定元素存放位置顿膨,
//擴(kuò)容前的元素地址為 (oldCap - 1) & e.hash ,所以這里的新的地址只有兩種可能锅锨,一是地址不變,
//二是變?yōu)?老位置+oldCap
                    else if (e instanceof TreeNode)
                        ((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
                    else { // preserve order
                        Node<K,V> loHead = null, loTail = null;
                        Node<K,V> hiHead = null, hiTail = null;
                        Node<K,V> next;

/* 這里如果判斷成立恋沃,那么該元素的地址在新的數(shù)組中就不會改變必搞。因為oldCap的最高位的1,在e.hash對應(yīng)的位上為0囊咏,所以擴(kuò)容后得到的地址是一樣的恕洲,位置不會改變 ,在后面的代碼的執(zhí)行中會放到loHead中去梅割,最后賦值給newTab[j]霜第;
如果判斷不成立,那么該元素的地址變?yōu)?原下標(biāo)位置+oldCap户辞,也就是lodCap最高位的1泌类,在e.hash對應(yīng)的位置上也為1,所以擴(kuò)容后的地址改變了底燎,在后面的代碼中會放到hiHead中刃榨,最后賦值給newTab[j + oldCap]
             舉個栗子來說一下上面的兩種情況:
            設(shè):oldCap=16 二進(jìn)制為:0001 0000
                oldCap-1=15 二進(jìn)制為:0000 1111
                e1.hash=10 二進(jìn)制為:0000 1010
                e2.hash=26 二進(jìn)制為:0101 1010
            e1在擴(kuò)容前的位置為:e1.hash & oldCap-1  結(jié)果為:0000 1010 
            e2在擴(kuò)容前的位置為:e2.hash & oldCap-1  結(jié)果為:0000 1010 
            結(jié)果相同弹砚,所以e1和e2在擴(kuò)容前在同一個鏈表上,這是擴(kuò)容之前的狀態(tài)枢希。

    現(xiàn)在擴(kuò)容后桌吃,需要重新計算元素的位置,在擴(kuò)容前的鏈表中計算地址的方式為e.hash & oldCap-1
    那么在擴(kuò)容后應(yīng)該也這么計算呀苞轿,擴(kuò)容后的容量為oldCap*2=32 0010 0000 newCap=32茅诱,新的計算
    方式應(yīng)該為
    e1.hash & newCap-1 
    即:0000 1010 & 0001 1111 
    結(jié)果為0000 1010與擴(kuò)容前的位置完全一樣。
    e2.hash & newCap-1 
    即:0101 1010 & 0001 1111 
    結(jié)果為0001 1010,為擴(kuò)容前位置+oldCap呕屎。
    而這里卻沒有e.hash & newCap-1 而是 e.hash & oldCap让簿,其實這兩個是等效的,都是判斷倒數(shù)第五位
    是0秀睛,還是1尔当。如果是0,則位置不變蹂安,是1則位置改變?yōu)閿U(kuò)容前位置+oldCap椭迎。
            再來分析下loTail loHead這兩個的執(zhí)行過程(假設(shè)(e.hash & oldCap) == 0成立):
            第一次執(zhí)行:
            e指向oldTab[j]所指向的node對象,即e指向該位置上鏈表的第一個元素
            loTail為空,所以loHead指向與e相同的node對象田盈,然后loTail也指向了同一個node對象畜号。
            最后,在判斷條件e指向next允瞧,就是指向oldTab鏈表中的第二個元素
            第二次執(zhí)行:
            lotail不為null简软,所以lotail.next指向e,這里其實是lotail指向的node對象的next指向e述暂,
            也可以說是痹升,loHead的next指向了e,就是指向了oldTab鏈表中第二個元素畦韭。此時loHead指向        
            的node變成了一個長度為2的鏈表疼蛾。然后lotail=e也就是指向了鏈表中第二個元素的地址。
            第三次執(zhí)行:
            與第二次執(zhí)行類似艺配,loHead上的鏈表長度變?yōu)?察郁,又增加了一個node,loTail指向新增的node
               ......
            hiTail與hiHead的執(zhí)行過程與以上相同转唉,這里就不再做解釋了皮钠。
            由此可以看出,loHead是用來保存新鏈表上的頭元素的赠法,loTail是用來保存尾元素的鳞芙,直到遍            
            歷完鏈表。   這是(e.hash & oldCap) == 0成立的時候。
            (e.hash & oldCap) == 0不成立的情況也相同原朝,其實就是把oldCap遍歷成兩個新的鏈表驯嘱,
            通過loHead和hiHead來保存鏈表的頭結(jié)點,然后將兩個頭結(jié)點放到newTab[j]與 
            newTab[j+oldCap]上面去      
*/
                              do {
                                next = e.next;
                            if ((e.hash & oldCap) == 0) {  
                                if (loTail == null)
                                    loHead = e;
                                else
                                    loTail.next = e;
                                loTail = e;
                            }
                            else {
                                if (hiTail == null)
                                    hiHead = e;
                                else
                                    hiTail.next = e;
                                hiTail = e;
                            }
                        } while ((e = next) != null);
                        if (loTail != null) {
                            loTail.next = null;   //尾節(jié)點的next設(shè)置為空
                            newTab[j] = loHead;
                        }
                        if (hiTail != null) {
                            hiTail.next = null;   //尾節(jié)點的next設(shè)置為空
                            newTab[j + oldCap] = hiHead;
                        }
                    }
                }
            }
        }
        return newTab;
    }

HashMap仍然是不安全的,所以在多線程并發(fā)條件下推薦使用ConcurrentHashMap喳坠。

最后

感謝你看到這里鞠评,看完有什么的不懂的可以在評論區(qū)問我,覺得文章對你有幫助的話記得給我點個贊壕鹉,每天都會分享java相關(guān)技術(shù)文章或行業(yè)資訊剃幌,歡迎大家關(guān)注和轉(zhuǎn)發(fā)文章!

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末晾浴,一起剝皮案震驚了整個濱河市负乡,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌脊凰,老刑警劉巖抖棘,帶你破解...
    沈念sama閱讀 217,185評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異狸涌,居然都是意外死亡切省,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,652評論 3 393
  • 文/潘曉璐 我一進(jìn)店門帕胆,熙熙樓的掌柜王于貴愁眉苦臉地迎上來朝捆,“玉大人,你說我怎么就攤上這事懒豹≤脚蹋” “怎么了?”我有些...
    開封第一講書人閱讀 163,524評論 0 353
  • 文/不壞的土叔 我叫張陵脸秽,是天一觀的道長何陆。 經(jīng)常有香客問我,道長豹储,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,339評論 1 293
  • 正文 為了忘掉前任淘这,我火速辦了婚禮剥扣,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘铝穷。我一直安慰自己钠怯,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,387評論 6 391
  • 文/花漫 我一把揭開白布曙聂。 她就那樣靜靜地躺著晦炊,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上断国,一...
    開封第一講書人閱讀 51,287評論 1 301
  • 那天贤姆,我揣著相機(jī)與錄音,去河邊找鬼稳衬。 笑死霞捡,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的薄疚。 我是一名探鬼主播碧信,決...
    沈念sama閱讀 40,130評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼街夭!你這毒婦竟也來了砰碴?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,985評論 0 275
  • 序言:老撾萬榮一對情侶失蹤板丽,失蹤者是張志新(化名)和其女友劉穎呈枉,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體檐什,經(jīng)...
    沈念sama閱讀 45,420評論 1 313
  • 正文 獨居荒郊野嶺守林人離奇死亡碴卧,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,617評論 3 334
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了乃正。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片住册。...
    茶點故事閱讀 39,779評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖瓮具,靈堂內(nèi)的尸體忽然破棺而出荧飞,到底是詐尸還是另有隱情,我是刑警寧澤名党,帶...
    沈念sama閱讀 35,477評論 5 345
  • 正文 年R本政府宣布叹阔,位于F島的核電站,受9級特大地震影響传睹,放射性物質(zhì)發(fā)生泄漏耳幢。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,088評論 3 328
  • 文/蒙蒙 一欧啤、第九天 我趴在偏房一處隱蔽的房頂上張望睛藻。 院中可真熱鬧,春花似錦邢隧、人聲如沸店印。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,716評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽按摘。三九已至包券,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間炫贤,已是汗流浹背溅固。 一陣腳步聲響...
    開封第一講書人閱讀 32,857評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留照激,地道東北人发魄。 一個月前我還...
    沈念sama閱讀 47,876評論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像俩垃,于是被迫代替她去往敵國和親励幼。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,700評論 2 354