JDK1.7-HashMap源碼分析

如果我在介紹集合的時候不介紹HashMap我相信一定會有人覺得我是個奇葩驱敲。畢竟是這么這么重要的類嘛载绿。本篇開始進入Map階段过蹂,相應(yīng)地會提到HashMap,ConcurrentHashMap,TreeMap等冷尉。作為實習(xí)以及平時日常使用的出現(xiàn)頻率最高的集合工具類豁跑,他被使用的場景我已經(jīng)無須再描述了。因為其的重要性,在本篇我們將關(guān)注它的任何一個細枝末節(jié)固额。

==因為HashMap在JDK1.7和1.8的實現(xiàn)中變化比較大,所以我們先介紹1.7中的HashMap==

源碼分析

注釋導(dǎo)讀

  • 允許null的key和null的value
  • 不保證遍歷順序骏掀,多次遍歷順序可能不一致
  • 提供常量的時間復(fù)雜度,希望在合適時候設(shè)置初始容量(不宜過大或者過小)
  • 0.75的factor一般情況不希望改變
  • 能避免就盡量避免rehash
  • 不是線程安全柱告,如果要求線程安全可以使用Collections.synchronizedMap()
  • 遍歷過程一旦改變元素就立馬拋出錯誤

類定義

public class HashMap<K,V>
extends AbstractMap<K,V>
implements Map<K,V>, Cloneable, Serializable

對于AbstractMap如果現(xiàn)在介紹的話到后面可能會忘記截驮,所以就在HashMap引用父類相關(guān)方法的時候在分別介紹。

map.JPG

Map接口的所有內(nèi)容都在上圖末荐,上層接口提供的都是一些通用的方法侧纯,重點是這個Entry類新锈。
Entry接口其實作為一個<K,V>的對出現(xiàn)甲脏,代表了這一對pair的組合,它有定義自己的getKey()和getValue()方法,同時也有自己的equals和hashCode方法的復(fù)寫妹笆。在Map類內(nèi)部對于數(shù)據(jù)的操作都要借助于Entry來進行块请,它才是Map類內(nèi)部真正核心的局部容器。

基本屬性

    /**
     * 初始容量拳缠,一定是2的N次方
     */
    static final int DEFAULT_INITIAL_CAPACITY = 16;
    /**
     * 最大容量
     */
    static final int MAXIMUM_CAPACITY = 1 << 30;
    /**
     * 默認的因子.
     */
    static final float DEFAULT_LOAD_FACTOR = 0.75f;
    /**
     * 承載Entry數(shù)據(jù)的核心數(shù)組
     */
    transient Entry<K,V>[] table;
    /**
     * Map中元素的數(shù)量
     */
    transient int size;
    /**
     * 需要進行擴容的閾值
     */
    int threshold;
    /**
     * 實際容器的因子(不提供的話就用DEFAULT_LOAD_FACTOR)
     */
    final float loadFactor;
    /**
     * 結(jié)構(gòu)內(nèi)容更新的次數(shù)(遍歷時候使用)
     */
    transient int modCount;
    /**
     * 系統(tǒng)默認的擴容閾值
     */
    static final int ALTERNATIVE_HASHING_THRESHOLD_DEFAULT = Integer.MAX_VALUE;
    /**
     * 如果這個值為true墩新,就使用另外一個hash算法來減少碰撞
     */
    transient boolean useAltHashing;
    /**
     * 為hash算法提供一個種子
     */
    transient final int hashSeed = sun.misc.Hashing.randomHashSeed(this);
    /**
     * 為獲得Entry提供一種方便的操作結(jié)構(gòu)
     */
    private transient Set<Map.Entry<K,V>> entrySet = null;

核心內(nèi)部類

    //核心靜態(tài)內(nèi)部類Entry
    static class Entry<K,V> implements Map.Entry<K,V> {
        final K key;
        V value;
        //因為HashMap是采用鏈表法處理哈希沖突的,所以Entry需要有一個指向下一個節(jié)點的指針
        Entry<K,V> next;
        int hash;

        /**
         * Creates new entry.
         */
        Entry(int h, K k, V v, Entry<K,V> n) {
            value = v;
            next = n;
            key = k;
            hash = h;
        }
        //final修飾主要為了防止子類覆蓋
        public final K getKey() {
            return key;
        }

        public final V getValue() {
            return value;
        }

        public final V setValue(V newValue) {
            V oldValue = value;
            value = newValue;
            return oldValue;
        }

        public final boolean equals(Object o) {
            if (!(o instanceof Map.Entry))
                return false;
            Map.Entry e = (Map.Entry)o; //這個步驟上面一定要有instanceof判斷窟坐,否則出現(xiàn)ClassCastException
            Object k1 = getKey();
            Object k2 = e.getKey();
            //這里值得學(xué)習(xí)的地方就是時刻保持對Null的警惕
            if (k1 == k2 || (k1 != null && k1.equals(k2))) {
                Object v1 = getValue();
                Object v2 = e.getValue();
                if (v1 == v2 || (v1 != null && v1.equals(v2)))
                    return true;
            }
            return false;
        }
        //hashCode的計算就是要保證key和value全部相等(要考慮null的情況)
        public final int hashCode() {
            return (key==null   ? 0 : key.hashCode()) ^
                   (value==null ? 0 : value.hashCode());
        }

        public final String toString() {
            return getKey() + "=" + getValue();
        }

        /**
         * 在put操作調(diào)用的時候如果存在相同的key就觸發(fā)下面的操作
         */
        void recordAccess(HashMap<K,V> m) {
        }

        /**
         * 當entry被移除的時候觸發(fā)下面操作
         */
        void recordRemoval(HashMap<K,V> m) {
        }
    }
    
    //內(nèi)部抽象類HashIterator
    private abstract class HashIterator<E> implements Iterator<E> {
        Entry<K,V> next;        // 下一個返回的entry
        int expectedModCount;   // 為了快速失敗
        int index;              // 當前的slot位置
        Entry<K,V> current;     // 當前的entry

        HashIterator() {
            expectedModCount = modCount;
            if (size > 0) { // advance to first entry
                Entry[] t = table;
                
                //定位到第一個位置不為null的slot,我們知道HashMap內(nèi)部的容器是一個數(shù)組海渊,數(shù)組的實體為Entry,而每一個Entry又相當于LinkedList 的Node節(jié)點一樣哲鸳,
                //它后面可能跟了很多Entry(鏈表法)臣疑,所以在遍歷的時候肯定需要定位到第一個不為空的slot
                //圖解見下
                while (index < t.length && (next = t[index++]) == null)
                    ;
            }
        }

        public final boolean hasNext() {
            return next != null;
        }

        final Entry<K,V> nextEntry() {
        //遍歷過程如果進行結(jié)構(gòu)改變將直接拋出錯誤
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
            Entry<K,V> e = next;
            if (e == null)
                throw new NoSuchElementException();
            //如果當前節(jié)點是最后一個節(jié)點
            if ((next = e.next) == null) {
                Entry[] t = table;
                //繼續(xù)將index定位到下一個不為null的slot
                while (index < t.length && (next = t[index++]) == null)
                    ;
            }
            current = e;
            return e;
        }

        public void remove() {
            if (current == null) //多次remove同一對象就報錯
                throw new IllegalStateException();
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
            Object k = current.key;
            current = null; //help GC
            //這個remove操作我們后面講
            HashMap.this.removeEntryForKey(k);
            expectedModCount = modCount;
        }
    }
    //這三個方法完全是基于上面的抽象類
    private final class ValueIterator extends HashIterator<V> {
        public V next() {
            return nextEntry().value;
        }
    }

    private final class KeyIterator extends HashIterator<K> {
        public K next() {
            return nextEntry().getKey();
        }
    }

    private final class EntryIterator extends HashIterator<Map.Entry<K,V>> {
        public Map.Entry<K,V> next() {
            return nextEntry();
        }
    }
    
    private final class KeySet extends AbstractSet<K> {
        public Iterator<K> iterator() {
            return newKeyIterator();//KeySet->KeyIterator->HashIterator
        }
        public int size() {
            return size;
        }
        public boolean contains(Object o) {
            return containsKey(o);
        }
        public boolean remove(Object o) {
            return HashMap.this.removeEntryForKey(o) != null;
        }
        public void clear() {
            HashMap.this.clear();
        }
    }
    
    private final class Values extends AbstractCollection<V> {
        public Iterator<V> iterator() {
            return newValueIterator();
        }
        public int size() {
            return size;
        }
        public boolean contains(Object o) {
            return containsValue(o);
        }
        public void clear() {
            HashMap.this.clear();
        }
    }
    
    private final class EntrySet extends AbstractSet<Map.Entry<K,V>> {
        public Iterator<Map.Entry<K,V>> iterator() {
            return newEntryIterator();
        }
        public boolean contains(Object o) {
            if (!(o instanceof Map.Entry))
                return false;
            Map.Entry<K,V> e = (Map.Entry<K,V>) o;
            Entry<K,V> candidate = getEntry(e.getKey());
            return candidate != null && candidate.equals(e);
        }
        public boolean remove(Object o) {
            return removeMapping(o) != null;
        }
        public int size() {
            return size;
        }
        public void clear() {
            HashMap.this.clear();
        }
    }
    無論是key還是value的單獨操作,都是依賴于Entry接口的徙菠,提供這些其他的接口就是為了方便我們單獨處理key或者value讯沈,因為我們再使用map的時候一般不會直接使用Entry
HashMap.JPG

所以通過Iterator進行遍歷的時候順序就是先遍歷slot:0里面的每一個entry,然后遍歷slot:2里面的對象然后依次進行婿奔。

構(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;
        //loadFactory要合法
        if (loadFactor <= 0 || Float.isNaN(loadFactor))
            throw new IllegalArgumentException("Illegal load factor: " +   loadFactor);

        // 找到一個最小的2的N次冪使其大于等于 initialCapacity
        // 例如: initialCapacity = 2 -> capacity = 2
        // 例如: initialCapacity = 3 -> capacity = 4
        // 例如: initialCapacity = 15 -> capacity = 16
        
        int capacity = 1; 
        while (capacity < initialCapacity)
            capacity <<= 1;

        this.loadFactor = loadFactor;
        threshold = (int)Math.min(capacity * loadFactor, MAXIMUM_CAPACITY + 1);
        table = new Entry[capacity];
        //當現(xiàn)在的容量超過了ALTERNATIVE_HASHING_THRESHOLD值后就采用不同的Hash策略
        useAltHashing = sun.misc.VM.isBooted() &&
                (capacity >= Holder.ALTERNATIVE_HASHING_THRESHOLD);
        init(); //空方法缺狠,供子類擴展
    }
    public HashMap(int initialCapacity) {
        this(initialCapacity, DEFAULT_LOAD_FACTOR);
    }

    /**
     * 默認就是16,0.75f的組合
     */
    public HashMap() {
        this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR);
    }
    public HashMap(Map<? extends K, ? extends V> m) {
        //m.size() / DEFAULT_LOAD_FACTOR) + 1:在不進行擴容的前提下的最小的閾值
        this(Math.max((int) (m.size() / DEFAULT_LOAD_FACTOR) + 1,
            DEFAULT_INITIAL_CAPACITY), DEFAULT_LOAD_FACTOR);
        putAllForCreate(m); //后面統(tǒng)一看這些插入操作
    }

常用方法

    說道常用方法我就忍不住要先說put和get這兩個高頻出現(xiàn)的方法了。哈哈哈哈
    
    public V put(K key, V value) {
        if (key == null) //允許null的key和null的value
            return putForNullKey(value);//實現(xiàn)見下
        int hash = hash(key);
        int i = indexFor(hash, table.length);
        for (Entry<K,V> e = table[i]; e != null; e = e.next) {
            Object k;
            //保證hash值和key值全部相同才會進行覆蓋
            if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
                V oldValue = e.value;
                e.value = value;
                e.recordAccess(this);
                return oldValue;
            }
        }

        modCount++;
        addEntry(hash, key, value, i);
        return null;
    }
    //統(tǒng)一把所有的key為Null的值放在slot:0的位置
    private V putForNullKey(V value) {
        for (Entry<K,V> e = table[0]; e != null; e = e.next) {
            if (e.key == null) { //初始化的時候已經(jīng)初始化過table了所以不用擔心e = null導(dǎo)致NPE
                V oldValue = e.value;
                e.value = value;
                e.recordAccess(this);//沒有實現(xiàn)萍摊,留給子類實現(xiàn)
                return oldValue;
            }
        }
        modCount++;
        addEntry(0, null, value, 0);
        return null;
    }
    //bucketIndex及我所說的slot
    void addEntry(int hash, K key, V value, int bucketIndex) {
        //如果超過了閾值并且要插入的位置已經(jīng)有元素存在就擴容(我們在日常使用中應(yīng)在進行避免擴容挤茄,能指定盡量指定HashMap的初始容量)
        if ((size >= threshold) && (null != table[bucketIndex])) {
            resize(2 * table.length); //擴容為原來的2倍
            hash = (null != key) ? hash(key) : 0;
            bucketIndex = indexFor(hash, table.length); //重新定位到目標bucketIndex
        }
        //前面都是定位擴容操作,最后就是將節(jié)點連接到table數(shù)組中
        createEntry(hash, key, value, bucketIndex);
    }
    //在增加Entry的時候直接進行連接
    void createEntry(int hash, K key, V value, int bucketIndex) {
        Entry<K,V> e = table[bucketIndex];
        table[bucketIndex] = new Entry<>(hash, key, value, e);
        size++;
    }
    
    void resize(int newCapacity) {
        Entry[] oldTable = table;
        int oldCapacity = oldTable.length;
        //原容量已經(jīng)最大了就不進行擴容了,但是閾值調(diào)整為Integer的最大值
        if (oldCapacity == MAXIMUM_CAPACITY) {
            threshold = Integer.MAX_VALUE;
            return;
        }

        Entry[] newTable = new Entry[newCapacity];
        boolean oldAltHashing = useAltHashing;
        useAltHashing |= sun.misc.VM.isBooted() &&
                (newCapacity >= Holder.ALTERNATIVE_HASHING_THRESHOLD);
        //如果兩個值一樣rehash=false, 不一樣的話rehash=true
        //rehash是否進行根據(jù)現(xiàn)階段的調(diào)整策略
        boolean rehash = oldAltHashing ^ useAltHashing;
        transfer(newTable, rehash); //將原來的元素移到新的table中
        table = newTable;
        //table都整體增長了所以閾值也要進行調(diào)整
        threshold = (int)Math.min(newCapacity * loadFactor, MAXIMUM_CAPACITY + 1);
    }
    
    void transfer(Entry[] newTable, boolean rehash) {
        int newCapacity = newTable.length;
        for (Entry<K,V> e : table) {
            while(null != e) { //在每一個slot上進行遍歷取到所有元素
                Entry<K,V> next = e.next;
                if (rehash) { //根據(jù)參數(shù)重新計算hash值
                    e.hash = null == e.key ? 0 : hash(e.key);
                }//算出要進行添加的slot位置
                int i = indexFor(e.hash, newCapacity);
                //將新元素作為頭結(jié)點進行添加冰木,不作為尾節(jié)點添加的原因是這樣簡單穷劈,而且不用考慮空元素
                e.next = newTable[i];
                newTable[i] = e;
                e = next;
            }
        }
    }
    //哈希散列算法(有人可能會問,Object對象都有自己的hashCode()方法了問什么還再實現(xiàn)一個,這個Hash算法是為了是元素分布更在均衡囚衔,減少碰撞挖腰。)
    final int hash(Object k) {
        int h = 0;
        if (useAltHashing) { //加入JVM自己的實現(xiàn)
            if (k instanceof String) {
                return sun.misc.Hashing.stringHash32((String) k);
            }
            h = hashSeed;
        }

        h ^= k.hashCode();
        //對以下具體hash算法的原理實現(xiàn)不太了解
        h ^= (h >>> 20) ^ (h >>> 12);
        return h ^ (h >>> 7) ^ (h >>> 4);
    }
    /**
     * 根據(jù)Hash值取得其在table中應(yīng)該插入的位置,這里實現(xiàn)極為巧妙练湿,也解釋了為什么要求table的長度一定是2的n次方
     * 初始化capacity = 16,轉(zhuǎn)換為二級制就是10000,假設(shè)目前一個元素的hash值為80猴仑,也就是1010000. 
     * 現(xiàn)在 16 & 80 = 0001111 & 1010000 = 0000000 = 0(十進制) 所以插入位置就是0 這種巧妙就是利用了數(shù)組長度-1后的所有二級制為全是1的特性,巧妙實現(xiàn)了取余的算法肥哎。
     * 因為硬件內(nèi)部底層是支持&運算的(數(shù)字電路)辽俗,所以這樣操作效率比%高。
     */
    static int indexFor(int h, int length) {
        return h & (length-1);
    }
    
    =======================================================
    put操作相關(guān)的方法都在上面進行了介紹篡诽,下面讓我們看看另外一個高頻使用方法get吧崖飘。
    public V get(Object key) {
        if (key == null)
            return getForNullKey(); // null key
        Entry<K,V> entry = getEntry(key);
    
        return null == entry ? null : entry.getValue();
    }
    
    private V getForNullKey() {
        //在上面的介紹中我們知道null key全部放在table[0]的位置
        for (Entry<K,V> e = table[0]; e != null; e = e.next) {
            if (e.key == null)
                return e.value;
        }
        return null;
    }
    
    final Entry<K,V> getEntry(Object key) {
        int hash = (key == null) ? 0 : hash(key);
        //第一步定位slot
        for (Entry<K,V> e = table[indexFor(hash, table.length)];
             e != null;
             e = e.next) {
            Object k;
            //注意這里的判斷,hash相等是必要條件(Entry的hash已經(jīng)被復(fù)寫過杈女,我們在插入元素的使用朱浴,Entry的hash值即為其key的hash值),key相等或者滿足equals都可以
            if (e.hash == hash &&
                ((k = e.key) == key || (key != null && key.equals(k))))
                return e;
        }
        return null;
    }
    因為get方法不包括擴容等操作达椰,也不會改變現(xiàn)在的結(jié)構(gòu)翰蠢,所以比較簡單。
    我們再來看看最后一個高頻方法contains
    public boolean containsKey(Object key) {
        return getEntry(key) != null;
    }
    
    public boolean containsValue(Object value) {
        if (value == null)
            return containsNullValue();
        //這里沒有什么技巧啰劲,只不過我覺得tab應(yīng)該用final修飾一下會更好
        Entry[] tab = table;
        for (int i = 0; i < tab.length ; i++)
            for (Entry e = tab[i] ; e != null ; e = e.next)
                if (value.equals(e.value))
                    return true;
        return false;
    }
    //把上面的方法最后的比較從equals換成了 == null 而已
    private boolean containsNullValue() {
        Entry[] tab = table;
        for (int i = 0; i < tab.length ; i++)
            for (Entry e = tab[i] ; e != null ; e = e.next)
                if (e.value == null)
                    return true;
        return false;
    }
    
    public V remove(Object key) {
        Entry<K,V> e = removeEntryForKey(key);
        return (e == null ? null : e.value);
    }
    
    final Entry<K,V> removeEntryForKey(Object key) {
        int hash = (key == null) ? 0 : hash(key);
        int i = indexFor(hash, table.length);
        Entry<K,V> prev = table[i];
        Entry<K,V> e = prev;

        while (e != null) {
            Entry<K,V> next = e.next;
            Object k;
            if (e.hash == hash &&
                ((k = e.key) == key || (key != null && key.equals(k)))) {
                modCount++;
                size--;
                //如果要移除的元素是列表的第一個元素
                if (prev == e)
                    table[i] = next;
                else
                    prev.next = next;
                e.recordRemoval(this); //需要子類實現(xiàn)
                return e; //這里e仍然可能保留了指向后面元素的指針梁沧,我覺得應(yīng)該加e.next = null
            }
            prev = e;
            e = next;
        }

        return e;
    }
    
    public void clear() {
        modCount++; //這里可不是modCount變了很多噢
        Entry[] tab = table;
        for (int i = 0; i < tab.length; i++)
            tab[i] = null;
        size = 0;
    }
    
    //這個克隆是淺克隆(Entry還是原來的Entry,指針是一樣的)
    public Object clone() {
        HashMap<K,V> result = null;
        try { 
            result = (HashMap<K,V>)super.clone();
        } catch (CloneNotSupportedException e) {
            // assert false;
        }
        result.table = new Entry[table.length];
        result.entrySet = null;
        result.modCount = 0;
        result.size = 0;
        result.init();
        //上面有介紹過這個方法
        result.putAllForCreate(this);

        return result;
    }
    

到這里蝇裤,JDK1.7的HashMap已經(jīng)全部都介紹完了廷支。我們從源碼中看不到任何關(guān)于同步的操作,所以多線程環(huán)境下使用的話還是需要多點注意的栓辜。下一篇我會講講JDK1.8中的HashMap恋拍,敬請期待.

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市啃憎,隨后出現(xiàn)的幾起案子芝囤,更是在濱河造成了極大的恐慌,老刑警劉巖辛萍,帶你破解...
    沈念sama閱讀 212,185評論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件悯姊,死亡現(xiàn)場離奇詭異,居然都是意外死亡贩毕,警方通過查閱死者的電腦和手機悯许,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,445評論 3 385
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來辉阶,“玉大人先壕,你說我怎么就攤上這事瘩扼。” “怎么了垃僚?”我有些...
    開封第一講書人閱讀 157,684評論 0 348
  • 文/不壞的土叔 我叫張陵集绰,是天一觀的道長。 經(jīng)常有香客問我谆棺,道長栽燕,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,564評論 1 284
  • 正文 為了忘掉前任改淑,我火速辦了婚禮碍岔,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘朵夏。我一直安慰自己蔼啦,他們只是感情好,可當我...
    茶點故事閱讀 65,681評論 6 386
  • 文/花漫 我一把揭開白布仰猖。 她就那樣靜靜地躺著捏肢,像睡著了一般。 火紅的嫁衣襯著肌膚如雪亮元。 梳的紋絲不亂的頭發(fā)上猛计,一...
    開封第一講書人閱讀 49,874評論 1 290
  • 那天唠摹,我揣著相機與錄音爆捞,去河邊找鬼。 笑死勾拉,一個胖子當著我的面吹牛煮甥,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播藕赞,決...
    沈念sama閱讀 39,025評論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼成肘,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了斧蜕?” 一聲冷哼從身側(cè)響起双霍,我...
    開封第一講書人閱讀 37,761評論 0 268
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎批销,沒想到半個月后洒闸,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,217評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡均芽,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,545評論 2 327
  • 正文 我和宋清朗相戀三年丘逸,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片掀宋。...
    茶點故事閱讀 38,694評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡深纲,死狀恐怖仲锄,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情湃鹊,我是刑警寧澤儒喊,帶...
    沈念sama閱讀 34,351評論 4 332
  • 正文 年R本政府宣布,位于F島的核電站币呵,受9級特大地震影響澄惊,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜富雅,卻給世界環(huán)境...
    茶點故事閱讀 39,988評論 3 315
  • 文/蒙蒙 一掸驱、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧没佑,春花似錦毕贼、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,778評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至啤贩,卻和暖如春待秃,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背痹屹。 一陣腳步聲響...
    開封第一講書人閱讀 32,007評論 1 266
  • 我被黑心中介騙來泰國打工章郁, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人志衍。 一個月前我還...
    沈念sama閱讀 46,427評論 2 360
  • 正文 我出身青樓暖庄,卻偏偏與公主長得像,于是被迫代替她去往敵國和親楼肪。 傳聞我的和親對象是個殘疾皇子培廓,可洞房花燭夜當晚...
    茶點故事閱讀 43,580評論 2 349

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