【轉(zhuǎn)】Java HashMap工作原理和實(shí)現(xiàn)

從本文你可以學(xué)習(xí)到:

  1. 什么時(shí)候會(huì)使用HashMap?他有什么特點(diǎn)玻墅?
  2. 你知道HashMap的工作原理嗎绍妨?
  3. 你知道get和put的原理嗎房待?equals()和hashCode()的都有什么作用?
  4. 你知道hash的實(shí)現(xiàn)嗎嚼酝?為什么要這樣實(shí)現(xiàn)浮还?
  5. 如果HashMap的大小超過了負(fù)載因子(load factor)定義的容量,怎么辦闽巩?

當(dāng)我們執(zhí)行下面的操作時(shí):

HashMap<String, Integer> map = new HashMap<String, Integer>();

map.put("語文", 1);

map.put("數(shù)學(xué)", 2);

map.put("英語", 3);

map.put("歷史", 4);

map.put("政治", 5);

map.put("地理", 6);

map.put("生物", 7);

map.put("化學(xué)", 8);

for(Entry<String, Integer> entry : map.entrySet()) {

    System.out.println(entry.getKey() + ": " + entry.getValue());

}

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

政治: 5
生物: 7
歷史: 4
數(shù)學(xué): 2
化學(xué): 8
語文: 1
英語: 3
地理: 6

發(fā)生了什么呢钧舌?下面是一個(gè)大致的結(jié)構(gòu),希望我們對(duì)HashMap的結(jié)構(gòu)有一個(gè)感性的認(rèn)識(shí):


hashmap

在官方文檔中是這樣描述HashMap的:

Hash table based implementation of the Map interface. This implementation provides all of the optional map operations, and permits null values and the null key. (The HashMap class is roughly equivalent to Hashtable, except that it is unsynchronized and permits nulls.) This class makes no guarantees as to the order of the map; in particular, it does not guarantee that the order will remain constant over time.

幾個(gè)關(guān)鍵的信息:基于Map接口實(shí)現(xiàn)涎跨、允許null鍵/值洼冻、非同步、不保證有序(比如插入的順序)隅很、也不保證序不隨時(shí)間變化撞牢。

2. 兩個(gè)重要的參數(shù)

在HashMap中有兩個(gè)很重要的參數(shù),容量(Capacity)和負(fù)載因子(Load factor)

  • Initial capacity The capacity is the number of buckets in the hash table, The initial capacity is simply the capacity at the time the hash table is created.
  • Load factor The load factor is a measure of how full the hash table is allowed to get before its capacity is automatically increased.

簡(jiǎn)單的說叔营,Capacity就是buckets的數(shù)目屋彪,Load factor就是buckets填滿程度的最大比例。如果對(duì)迭代性能要求很高的話不要把capacity設(shè)置過大绒尊,也不要把load factor設(shè)置過小畜挥。當(dāng)bucket填充的數(shù)目(即hashmap中元素的個(gè)數(shù))大于capacity*load factor時(shí)就需要調(diào)整buckets的數(shù)目為當(dāng)前的2倍。

3. put函數(shù)的實(shí)現(xiàn)

put函數(shù)大致的思路為:

  1. 對(duì)key的hashCode()做hash垒酬,然后再計(jì)算index;
  2. 如果沒碰撞直接放到bucket里砰嘁;
  3. 如果碰撞了件炉,以鏈表的形式存在buckets后;
  4. 如果碰撞導(dǎo)致鏈表過長(大于等于TREEIFY_THRESHOLD)矮湘,就把鏈表轉(zhuǎn)換成紅黑樹斟冕;
  5. 如果節(jié)點(diǎn)已經(jīng)存在就替換old value(保證key的唯一性)
  6. 如果bucket滿了(超過load factor*current capacity),就要resize缅阳。

具體代碼的實(shí)現(xiàn)如下:


public V put(K key, V value) {

    // 對(duì)key的hashCode()做hash

    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;

    // tab為空則創(chuàng)建

    if ((tab = table) == null || (n = tab.length) == 0)

        n = (tab = resize()).length;

    // 計(jì)算index磕蛇,并對(duì)null做處理

    if ((p = tab[i = (n - 1) & hash]) == null)

        tab[i] = newNode(hash, key, value, null);

    else {

        Node<K,V> e; K k;

        // 節(jié)點(diǎn)存在

        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);

                    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;

            }

        }

        // 寫入

        if (e != null) { // existing mapping for key

            V oldValue = e.value;

            if (!onlyIfAbsent || oldValue == null)

                e.value = value;

            afterNodeAccess(e);

            return oldValue;

        }

    }

    ++modCount;

    // 超過load factor*current capacity,resize

    if (++size > threshold)

        resize();

    afterNodeInsertion(evict);

    return null;

}

4. get函數(shù)的實(shí)現(xiàn)

在理解了put之后十办,get就很簡(jiǎn)單了秀撇。大致思路如下:

  1. bucket里的第一個(gè)節(jié)點(diǎn),直接命中向族;
  2. 如果有沖突呵燕,則通過key.equals(k)去查找對(duì)應(yīng)的entry
    若為樹,則在樹中通過key.equals(k)查找件相,O(logn)再扭;
    若為鏈表,則在鏈表中通過key.equals(k)查找夜矗,O(n)泛范。

具體代碼的實(shí)現(xiàn)如下:


public V get(Object key) {

    Node<K,V> e;

    return (e = getNode(hash(key), key)) == null ? null : e.value;

}

final Node<K,V> getNode(int hash, Object key) {

    Node<K,V>[] tab; Node<K,V> first, e; int n; K k;

    if ((tab = table) != null && (n = tab.length) > 0 &&

        (first = tab[(n - 1) & hash]) != null) {

        // 直接命中

        if (first.hash == hash && // always check first node

            ((k = first.key) == key || (key != null && key.equals(k))))

            return first;

        // 未命中

        if ((e = first.next) != null) {

            // 在樹中g(shù)et

            if (first instanceof TreeNode)

                return ((TreeNode<K,V>)first).getTreeNode(hash, key);

            // 在鏈表中g(shù)et

            do {

                if (e.hash == hash &&

                    ((k = e.key) == key || (key != null && key.equals(k))))

                    return e;

            } while ((e = e.next) != null);

        }

    }

    return null;

}

5. hash函數(shù)的實(shí)現(xiàn)

在get和put的過程中,計(jì)算下標(biāo)時(shí)紊撕,先對(duì)hashCode進(jìn)行hash操作罢荡,然后再通過hash值進(jìn)一步計(jì)算下標(biāo),如下圖所示:


hash

在對(duì)hashCode()計(jì)算hash時(shí)具體實(shí)現(xiàn)是這樣的:


static final int hash(Object key) {

    int h;

    return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);

}

可以看到這個(gè)函數(shù)大概的作用就是:高16bit不變对扶,低16bit和高16bit做了一個(gè)異或区赵。其中代碼注釋是這樣寫的:

Computes key.hashCode() and spreads (XORs) higher bits of hash to lower. Because the table uses power-of-two masking, sets of hashes that vary only in bits above the current mask will always collide. (Among known examples are sets of Float keys holding consecutive whole numbers in small tables.) So we apply a transform that spreads the impact of higher bits downward. There is a tradeoff between speed, utility, and quality of bit-spreading. Because many common sets of hashes are already reasonably distributed (so don’t benefit from spreading), and because we use trees to handle large sets of collisions in bins, we just XOR some shifted bits in the cheapest possible way to reduce systematic lossage, as well as to incorporate impact of the highest bits that would otherwise never be used in index calculations because of table bounds.

在設(shè)計(jì)hash函數(shù)時(shí),因?yàn)槟壳暗膖able長度n為2的冪辩稽,而計(jì)算下標(biāo)的時(shí)候惧笛,是這樣實(shí)現(xiàn)的(使用&位操作,而非%求余):


(n - 1) & hash

設(shè)計(jì)者認(rèn)為這方法很容易發(fā)生碰撞逞泄。為什么這么說呢患整?不妨思考一下,在n - 1為15(0x1111)時(shí)喷众,其實(shí)散列真正生效的只是低4bit的有效位各谚,當(dāng)然容易碰撞了。

因此到千,設(shè)計(jì)者想了一個(gè)顧全大局的方法(綜合考慮了速度昌渤、作用、質(zhì)量)憔四,就是把高16bit和低16bit異或了一下膀息。設(shè)計(jì)者還解釋到因?yàn)楝F(xiàn)在大多數(shù)的hashCode的分布已經(jīng)很不錯(cuò)了般眉,就算是發(fā)生了碰撞也用O(logn)的tree去做了。僅僅異或一下潜支,既減少了系統(tǒng)的開銷甸赃,也不會(huì)造成的因?yàn)楦呶粵]有參與下標(biāo)的計(jì)算(table長度比較小時(shí)),從而引起的碰撞冗酿。

如果還是產(chǎn)生了頻繁的碰撞埠对,會(huì)發(fā)生什么問題呢?作者注釋說裁替,他們使用樹來處理頻繁的碰撞(we use trees to handle large sets of collisions in bins)项玛,在JEP-180中,描述了這個(gè)問題:

Improve the performance of java.util.HashMap under high hash-collision conditions by using balanced trees rather than linked lists to store map entries. Implement the same improvement in the LinkedHashMap class.

之前已經(jīng)提過弱判,在獲取HashMap的元素時(shí)襟沮,基本分兩步:

  1. 首先根據(jù)hashCode()做hash,然后確定bucket的index裕循;
  2. 如果bucket的節(jié)點(diǎn)的key不是我們需要的臣嚣,則通過keys.equals()在鏈中找。

在Java 8之前的實(shí)現(xiàn)中是用鏈表解決沖突的剥哑,在產(chǎn)生碰撞的情況下,進(jìn)行g(shù)et時(shí)淹父,兩步的時(shí)間復(fù)雜度是O(1)+O(n)株婴。因此,當(dāng)碰撞很厲害的時(shí)候n很大暑认,O(n)的速度顯然是影響速度的困介。

因此在Java 8中,利用紅黑樹替換鏈表蘸际,這樣復(fù)雜度就變成了O(1)+O(logn)了座哩,這樣在n很大的時(shí)候,能夠比較理想的解決這個(gè)問題粮彤,在Java 8:HashMap的性能提升一文中有性能測(cè)試的結(jié)果根穷。

6. RESIZE的實(shí)現(xiàn)

當(dāng)put時(shí),如果發(fā)現(xiàn)目前的bucket占用程度已經(jīng)超過了Load Factor所希望的比例导坟,那么就會(huì)發(fā)生resize屿良。在resize的過程,簡(jiǎn)單的說就是把bucket擴(kuò)充為2倍惫周,之后重新計(jì)算index尘惧,把節(jié)點(diǎn)再放到新的bucket中。resize的注釋是這樣描述的:

Initializes or doubles table size. If null, allocates in accord with initial capacity target held in field threshold. Otherwise, because we are using power-of-two expansion, the elements from each bin must either stay at same index, or move with a power of two offset in the new table.

大致意思就是說递递,當(dāng)超過限制的時(shí)候會(huì)resize喷橙,然而又因?yàn)槲覀兪褂玫氖?次冪的擴(kuò)展(指長度擴(kuò)為原來2倍)啥么,所以,元素的位置要么是在原位置贰逾,要么是在原位置再移動(dòng)2次冪的位置饥臂。

怎么理解呢?例如我們從16擴(kuò)展為32時(shí)似踱,具體的變化如下所示:


rehash

因此元素在重新計(jì)算hash之后隅熙,因?yàn)閚變?yōu)?倍,那么n-1的mask范圍在高位多1bit(紅色)核芽,因此新的index就會(huì)發(fā)生這樣的變化:


resize

因此囚戚,我們?cè)跀U(kuò)充HashMap的時(shí)候,不需要重新計(jì)算hash轧简,只需要看看原來的hash值新增的那個(gè)bit是1還是0就好了驰坊,是0的話索引沒變,是1的話索引變成“原索引+oldCap”哮独∪剑可以看看下圖為16擴(kuò)充為32的resize示意圖:


resize16-32

這個(gè)設(shè)計(jì)確實(shí)非常的巧妙,既省去了重新計(jì)算hash值的時(shí)間皮璧,而且同時(shí)舟扎,由于新增的1bit是0還是1可以認(rèn)為是隨機(jī)的,因此resize的過程悴务,均勻的把之前的沖突的節(jié)點(diǎn)分散到新的bucket了睹限。

下面是代碼的具體實(shí)現(xiàn):


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ò)充了,就只好隨你碰撞去吧

        if (oldCap >= MAXIMUM_CAPACITY) {

            threshold = Integer.MAX_VALUE;

            return oldTab;

        }

        // 沒超過最大值讯檐,就擴(kuò)充為原來的2倍

        else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&

                 oldCap >= DEFAULT_INITIAL_CAPACITY)

            newThr = oldThr << 1; // double threshold

    }

    else if (oldThr > 0) // initial capacity was placed in threshold

        newCap = oldThr;

    else {               // zero initial threshold signifies using defaults

        newCap = DEFAULT_INITIAL_CAPACITY;

        newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);

    }

    // 計(jì)算新的resize上限

    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"})

        Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];

    table = newTab;

    if (oldTab != null) {

        // 把每個(gè)bucket都移動(dòng)到新的buckets中

        for (int j = 0; j < oldCap; ++j) {

            Node<K,V> e;

            if ((e = oldTab[j]) != null) {

                oldTab[j] = null;

                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 { // preserve order

                    Node<K,V> loHead = null, loTail = null;

                    Node<K,V> hiHead = null, hiTail = null;

                    Node<K,V> next;

                    do {

                        next = e.next;

                        // 原索引

                        if ((e.hash & oldCap) == 0) {

                            if (loTail == null)

                                loHead = e;

                            else

                                loTail.next = e;

                            loTail = e;

                        }

                        // 原索引+oldCap

                        else {

                            if (hiTail == null)

                                hiHead = e;

                            else

                                hiTail.next = e;

                            hiTail = e;

                        }

                    } while ((e = next) != null);

                    // 原索引放到bucket里

                    if (loTail != null) {

                        loTail.next = null;

                        newTab[j] = loHead;

                    }

                    // 原索引+oldCap放到bucket里

                    if (hiTail != null) {

                        hiTail.next = null;

                        newTab[j + oldCap] = hiHead;

                    }

                }

            }

        }

    }

    return newTab;

}

7. 總結(jié)

我們現(xiàn)在可以回答開始的幾個(gè)問題羡疗,加深對(duì)HashMap的理解:

1. 什么時(shí)候會(huì)使用HashMap?他有什么特點(diǎn)别洪?
是基于Map接口的實(shí)現(xiàn)叨恨,存儲(chǔ)鍵值對(duì)時(shí),它可以接收null的鍵值挖垛,是非同步的痒钝,HashMap存儲(chǔ)著Entry(hash, key, value, next)對(duì)象。

2. 你知道HashMap的工作原理嗎晕换?
通過hash的方法午乓,通過put和get存儲(chǔ)和獲取對(duì)象。存儲(chǔ)對(duì)象時(shí)闸准,我們將K/V傳給put方法時(shí)益愈,它調(diào)用hashCode計(jì)算hash從而得到bucket位置,進(jìn)一步存儲(chǔ),HashMap會(huì)根據(jù)當(dāng)前bucket的占用情況自動(dòng)調(diào)整容量(超過Load Facotr則resize為原來的2倍)蒸其。獲取對(duì)象時(shí)敏释,我們將K傳給get,它調(diào)用hashCode計(jì)算hash從而得到bucket位置摸袁,并進(jìn)一步調(diào)用equals()方法確定鍵值對(duì)钥顽。如果發(fā)生碰撞的時(shí)候,Hashmap通過鏈表將產(chǎn)生碰撞沖突的元素組織起來靠汁,在Java 8中蜂大,如果一個(gè)bucket中碰撞沖突的元素超過某個(gè)限制(默認(rèn)是8),則使用紅黑樹來替換鏈表蝶怔,從而提高速度奶浦。

3. 你知道get和put的原理嗎?equals()和hashCode()的都有什么作用踢星?
通過對(duì)key的hashCode()進(jìn)行hashing澳叉,并計(jì)算下標(biāo)( n-1 & hash),從而獲得buckets的位置沐悦。如果產(chǎn)生碰撞成洗,則利用key.equals()方法去鏈表或樹中去查找對(duì)應(yīng)的節(jié)點(diǎn)

4. 你知道hash的實(shí)現(xiàn)嗎?為什么要這樣實(shí)現(xiàn)藏否?
在Java 1.8的實(shí)現(xiàn)中瓶殃,是通過hashCode()的高16位異或低16位實(shí)現(xiàn)的:(h = k.hashCode()) ^ (h >>> 16),主要是從速度秕岛、功效碌燕、質(zhì)量來考慮的,這么做可以在bucket的n比較小的時(shí)候继薛,也能保證考慮到高低bit都參與到hash的計(jì)算中,同時(shí)不會(huì)有太大的開銷愈捅。

5. 如果HashMap的大小超過了負(fù)載因子(load factor)定義的容量遏考,怎么辦?
如果超過了負(fù)載因子(默認(rèn)0.75)蓝谨,則會(huì)重新resize一個(gè)原來長度兩倍的HashMap灌具,并且重新調(diào)用hash方法。

關(guān)于Java集合的小抄中是這樣描述的:

以Entry[]數(shù)組實(shí)現(xiàn)的哈希桶數(shù)組譬巫,用Key的哈希值取模桶數(shù)組的大小可得到數(shù)組下標(biāo)咖楣。

插入元素時(shí),如果兩條Key落在同一個(gè)桶(比如哈希值1和17取模16后都屬于第一個(gè)哈希桶)芦昔,我們稱之為哈希沖突诱贿。

JDK的做法是鏈表法,Entry用一個(gè)next屬性實(shí)現(xiàn)多個(gè)Entry以單向鏈表存放。查找哈希值為17的key時(shí)珠十,先定位到哈希桶料扰,然后鏈表遍歷桶里所有元素,逐個(gè)比較其Hash值然后key值焙蹭。

在JDK8里晒杈,新增默認(rèn)為8的閾值,當(dāng)一個(gè)桶里的Entry超過閥值孔厉,就不以單向鏈表而以紅黑樹來存放以加快Key的查找速度拯钻。

當(dāng)然,最好還是桶里只有一個(gè)元素撰豺,不用去比較粪般。所以默認(rèn)當(dāng)Entry數(shù)量達(dá)到桶數(shù)量的75%時(shí),哈希沖突已比較嚴(yán)重郑趁,就會(huì)成倍擴(kuò)容桶數(shù)組刊驴,并重新分配所有原來的Entry。擴(kuò)容成本不低寡润,所以也最好有個(gè)預(yù)估值捆憎。

取模用與操作(hash & (arrayLength-1))會(huì)比較快,所以數(shù)組的大小永遠(yuǎn)是2的N次方梭纹, 你隨便給一個(gè)初始值比如17會(huì)轉(zhuǎn)為32躲惰。默認(rèn)第一次放入元素時(shí)的初始值是16镣陕。

iterator()時(shí)順著哈希桶數(shù)組來遍歷烫扼,看起來是個(gè)亂序

原文摘自:https://yikun.github.io/2015/04/01/Java-HashMap%E5%B7%A5%E4%BD%9C%E5%8E%9F%E7%90%86%E5%8F%8A%E5%AE%9E%E7%8E%B0/

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市吼野,隨后出現(xiàn)的幾起案子绍载,更是在濱河造成了極大的恐慌诡宗,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,496評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件击儡,死亡現(xiàn)場(chǎng)離奇詭異塔沃,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)阳谍,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,407評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門蛀柴,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人矫夯,你說我怎么就攤上這事鸽疾。” “怎么了训貌?”我有些...
    開封第一講書人閱讀 162,632評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵制肮,是天一觀的道長。 經(jīng)常有香客問我,道長弄企,這世上最難降的妖魔是什么超燃? 我笑而不...
    開封第一講書人閱讀 58,180評(píng)論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮拘领,結(jié)果婚禮上意乓,老公的妹妹穿的比我還像新娘。我一直安慰自己约素,他們只是感情好届良,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,198評(píng)論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著圣猎,像睡著了一般士葫。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上送悔,一...
    開封第一講書人閱讀 51,165評(píng)論 1 299
  • 那天慢显,我揣著相機(jī)與錄音,去河邊找鬼欠啤。 笑死荚藻,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的洁段。 我是一名探鬼主播应狱,決...
    沈念sama閱讀 40,052評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼祠丝!你這毒婦竟也來了疾呻?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,910評(píng)論 0 274
  • 序言:老撾萬榮一對(duì)情侶失蹤写半,失蹤者是張志新(化名)和其女友劉穎岸蜗,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體叠蝇,經(jīng)...
    沈念sama閱讀 45,324評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡散吵,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,542評(píng)論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了蟆肆。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,711評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡晦款,死狀恐怖炎功,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情缓溅,我是刑警寧澤蛇损,帶...
    沈念sama閱讀 35,424評(píng)論 5 343
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響淤齐,放射性物質(zhì)發(fā)生泄漏股囊。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,017評(píng)論 3 326
  • 文/蒙蒙 一更啄、第九天 我趴在偏房一處隱蔽的房頂上張望稚疹。 院中可真熱鬧,春花似錦祭务、人聲如沸内狗。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,668評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽柳沙。三九已至,卻和暖如春拌倍,著一層夾襖步出監(jiān)牢的瞬間赂鲤,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,823評(píng)論 1 269
  • 我被黑心中介騙來泰國打工柱恤, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留数初,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,722評(píng)論 2 368
  • 正文 我出身青樓膨更,卻偏偏與公主長得像妙真,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子荚守,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,611評(píng)論 2 353