HashMap 源碼分析(JDK1.7)

HashMap 簡介

HashMap 是一個散列表经磅,它存儲的內(nèi)容是鍵值對(key-value)映射。

HashMap 實現(xiàn)了 Map 接口造烁,根據(jù)鍵的 HashCode 值存儲數(shù)據(jù)欺嗤,具有很快的訪問速度,最多允許一條記錄的鍵為 null综苔,不支持線程同步。

HashMap 是無序的位岔,即不會記錄插入的順序如筛。

HashMap 繼承于AbstractMap泄私,實現(xiàn)了 Map拉庶、Cloneable、java.io.Serializable 接口粟判。

HashMap 的繼承關(guān)系

首先看下集合框架圖擦剑,Map 和 Collection 屬于兩個不同的結(jié)構(gòu)妖胀,這里容易弄混淆。

結(jié)構(gòu)框架圖

HashMap 的繼承關(guān)系

HashMap繼承關(guān)系.png

HashMap 簡單使用

HashMap 的 key 與 value 類型可以相同也可以不同惠勒,可以是字符串(String)類型的 key 和 value赚抡,也可以是整型(Integer)的 key 和字符串(String)類型的 value。

    HashMap<String, Integer> hhs = new HashMap<>();
    hhs.put("aaa", 1);
    hhs.put("bbb", 2);
    hhs.put("aaa", 1);
    hhs.get("aaa");
    hhs.remove("aaa");

HashMap 的存儲結(jié)構(gòu)

在了解 HashMap 源碼之前纠屋,先來看下 HashMap 的存儲結(jié)構(gòu)圖涂臣。

HashMap存儲結(jié)構(gòu).png

可以看到左方為數(shù)組存儲方式,數(shù)組的方式特點是查詢快 [o(1)] 售担,但是增加和刪除比較慢 [o(n)] 赁遗。右方為鏈表的數(shù)據(jù)結(jié)構(gòu)署辉,鏈表數(shù)據(jù)結(jié)構(gòu)查詢慢 [o(n)] ,但是增加和刪除 [(o(1))] 比較快岩四。而 HashMap 采用兩種方式的結(jié)合哭尝。相當于折中方案,提高效率炫乓。

HashMap 的基本原理

1刚夺、首先判斷Key是否為 Null献丑,如果為 null末捣,直接查找 Enrty[0],如果不是 Null创橄,先計算 Key 的 HashCode箩做,然后經(jīng)過二次 Hash。得到 Hash 值妥畏,這里的 Hash 特征值是一個 int 值邦邦。

2、根據(jù)Hash值醉蚁,找到對應的數(shù)組燃辖,所以對 Entry[] 的長度 length 求余,得到的就是 Entry 數(shù)組的 index网棍。

3黔龟、找到對應的數(shù)組,就是找到了所在的鏈表滥玷,然后按照鏈表的操作對 Value 進行插入氏身、刪除和查詢操作。

HashMap 的構(gòu)造方法

首先來看 HashMap 的構(gòu)造函數(shù)

public HashMap() {
    this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR);
}


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;
    threshold = initialCapacity;
    init();
}

構(gòu)造函數(shù)中初始化了一個 loadFactor 變量惑畴,默認值 DEFAULT_LOAD_FACTOR 為 0.75蛋欣。 threshold 變量默認值為 16(數(shù) 1 左移四位得到) 表示為 Entry[] 數(shù)組的臨界長度。 loadFactor 和 threshold 決定是否要對 Entry[] 的容量進行調(diào)整如贷。 threshold 由 HashMap 數(shù)組長度和 loadFactor 相乘計算得出陷虎,例如數(shù)組長度為 16 ,loadFactor 為 0.75杠袱,則 threshold 應該為 12 尚猿。即當 HashMap 中的容量數(shù)據(jù)大于等于 12 的時候,則應該對 table 數(shù)組進行擴容處理霞掺。 此時初始化 threshold 默認為數(shù)組長度 16(后續(xù)操作會對其更改)谊路。 init() 為空調(diào)用方法。

可以看到構(gòu)造函數(shù)只是初始化了一些變量菩彬。

接下來 HashMap 的 put 操作則是 HashMap 原理的主要研究點缠劝。

public V put(K key, V value) {
    if (table == EMPTY_TABLE) {
     // table 數(shù)組為空則根據(jù) threshold 初始化數(shù)組潮梯,即為容量為 16 的數(shù)組,初始化完成后同時 threshold 更改為 數(shù)組長度 length * loadFactor
        inflateTable(threshold);
    }
    if (key == null) // key 為空 
        return putForNullKey(value);
    int hash = hash(key); // 對key 進行 hash 處理
    int i = indexFor(hash, table.length); // 通過 hash 值拿到 key 對應數(shù)組的位置
    for (Entry<K,V> e = table[i]; e != null; e = e.next) { // 對應 Entry
        Object k;
        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;
}

這里可以看到惨恭,table[] 數(shù)組中存入的為 Entry秉馏,默認初始容量為 16 。根據(jù)上述數(shù)據(jù)和 Entry 結(jié)構(gòu)圖知道 Entry 數(shù)據(jù)保存了數(shù)據(jù)的 key 脱羡、value萝究、hash 、i(數(shù)組下標)的值锉罐。

傳入 key 為 null 的時候帆竹,會將數(shù)據(jù) Entry 數(shù)據(jù)存入 table[0] 的位置;

private V putForNullKey(V value) {
    for (Entry<K,V> e = table[0]; e != null; e = e.next) { 
        if (e.key == null) {// 替換 key 為 null 的value 
            V oldValue = e.value;
            e.value = value;
            e.recordAccess(this);
            return oldValue;
        }
    }
    modCount++;
    addEntry(0, null, value, 0);// 插入數(shù)據(jù)
    return null;
}

HashMap put方法的 hash 處理

如果key 不為空脓规,則對 key 進行 hash 操作栽连;

final int hash(Object k) {
    int h = hashSeed;
    if (0 != h && k instanceof String) {
        return sun.misc.Hashing.stringHash32((String) k);
    }

    h ^= k.hashCode();

    // This function ensures that hashCodes that differ only by
    // constant multiples at each bit position have a bounded
    // number of collisions (approximately 8 at default load factor).
    h ^= (h >>> 20) ^ (h >>> 12);
    return h ^ (h >>> 7) ^ (h >>> 4);
}

針對 key 獲取 hash 值,這里會再次進行一系列的邏輯操作侨舆, 這里注釋也對其進行了說明秒紧,對 hash 值進行二次操作是為了不同的 key 算出來的 hash 值盡可能不一樣,避免出現(xiàn) hash 沖突的情況

接下來挨下,拿到二次處理過的 hash 值生成對應 table 的下標 i熔恢;

static int indexFor(int h, int length) {
    // assert Integer.bitCount(length) == 1 : "length must be a non-zero power of 2";
    return h & (length-1);
}

length 為 table 數(shù)組的長度, h & (length-1) 實際為對數(shù)組長度取模運算(h % table.length()),例如臭笆,現(xiàn)在數(shù)組長度為 16叙淌,算出來的 hash 值為 15,與運算之后即為 15耗啦。這里其實 length - 1 后對相應低位二進制全部改變凿菩,而再和 hash 值的低位進行 & 運算,對應拿到的就是 hash 對應低位的值帜讲。即為對 length 的取模衅谷。

而這里對二次處理的 hash 值進行取模運算,是為了數(shù)據(jù)能夠盡可能均勻分布在 table 數(shù)組中似将。提高 table 的使用效率获黔。

到這里,已經(jīng)找到可以找到 key 值對應在 table 數(shù)組的位置在验,前面的處理總結(jié)下有兩個目的:

  1. 二次 hash 處理玷氏,避免出現(xiàn) hash 碰撞的情況。
  2. 對數(shù)組長度取模處理腋舌,使數(shù)據(jù)均勻分布在數(shù)組中盏触。

拿到了數(shù)組 table 的對應下標 index,取出對應的 Entry 數(shù)據(jù)。針對 Entey 鏈表進行遍歷赞辩,如果查找到對應的 key 則進行替換雌芽,否則,調(diào)用 addEntry 插入數(shù)據(jù)辨嗽。

接下來再看看 addEntry 方法世落;

void addEntry(int hash, K key, V value, int bucketIndex) {
    if ((size >= threshold) && (null != table[bucketIndex])) { // 當 size 達到 HashMap 臨界值了,調(diào)整 table 的長度
        resize(2 * table.length);
        hash = (null != key) ? hash(key) : 0;
        bucketIndex = indexFor(hash, table.length);
    }

    createEntry(hash, key, value, bucketIndex);
}

先看 createEntry 方法去創(chuàng)建數(shù)據(jù)糟需;

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++;
}

將對應數(shù)據(jù)放在 table 數(shù)組引用的節(jié)點處屉佳,假如原來 bucketIndex 處有相應的數(shù)據(jù),存入的數(shù)據(jù)的下一個數(shù)據(jù)指向的即時 e (原存儲數(shù)據(jù)洲押,即在第一位插入數(shù)據(jù))武花;

針對上述操作,下圖圖示展示:

HashMap_put_01.png

現(xiàn)在 HashMap 的存儲的數(shù)據(jù)如上圖所示诅诱,假設(shè)現(xiàn)在有個(k9,v9)的值需要插入到 HashMap 中髓堪。

當對 k9 進行 hash 等一系列運算之后,發(fā)現(xiàn)此時數(shù)據(jù)的下標為 1娘荡,即和 (k3,v3)驶沼、(k8炮沐,v8)的處理下標一致,進行如下存儲回怜;

HashMap_put_02.png

此時再次存儲 (k3大年,v10)的數(shù)據(jù),k3 通過 hash 之后算出來的下標肯定也是在 table[1] 處玉雾,替換進行如下存儲翔试;

HashMap_put_03.png

最后再插入一條(k10,v10)的數(shù)據(jù)复旬,算出下標為 5 垦缅,如下進行存儲;

HashMap_put_04.png

HashMap 的擴容處理

當 HashMap 里面存儲的數(shù)據(jù)到了一定的臨界值的時候驹碍,應當對 table 進行擴容處理壁涎;

剛剛上述 addEntry 方法中提到擴容處理情況;

    if ((size >= threshold) && (null != table[bucketIndex])) { // 當 size 達到 HashMap 臨界值了志秃,調(diào)整 table 的長度
        resize(2 * table.length);
        hash = (null != key) ? hash(key) : 0;
        bucketIndex = indexFor(hash, table.length);
    }

可以看到當 size 大于等于 threshold 臨界值的時候怔球,且當前的數(shù)據(jù)不為空的情況下,我們就要進行 table 擴容了浮还。threshold 我們知道由 loadFactor(加載因子)和 table 的容量共同決定竟坛,loadFactor 為大于 0 小于 1 的值,即此時已存數(shù)據(jù) size 到達 table 容量的 loadFactor 倍數(shù)的時候,就必須進行擴容了担汤。那么如何擴容的呢?

void resize(int newCapacity) {
    Entry[] oldTable = table;// 舊的table
    int oldCapacity = oldTable.length;
    if (oldCapacity == MAXIMUM_CAPACITY) {
        threshold = Integer.MAX_VALUE;
        return;
    }

    Entry[] newTable = new Entry[newCapacity];// 新table 容量翻倍
    transfer(newTable, initHashSeedAsNeeded(newCapacity));// 數(shù)據(jù)的轉(zhuǎn)移
    table = newTable;// newtable 賦值給 table
    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) {
            Entry<K,V> next = e.next;
            if (rehash) {
                e.hash = null == e.key ? 0 : hash(e.key);
            }
            int i = indexFor(e.hash, newCapacity);
            e.next = newTable[i];
            newTable[i] = e;
            e = next;
        }
    }
}

看到擴容處理不僅僅把 table 容量增長兩倍就完事了又官,原 table 中的數(shù)據(jù)該怎么存呢?

如果不進行更換的話漫试,假如對上圖所示數(shù)據(jù)進行(k9,v9)的數(shù)據(jù)查找六敬,因為 table 擴容后,二次 hash 結(jié)果和 table.length 取模后獲取的下標數(shù)據(jù)大概率是變化了驾荣,那樣就找不到數(shù)據(jù)了外构。

于是 transfer 方法拿到舊的 table 數(shù)據(jù),然后根據(jù) newTable 的容量對原數(shù)據(jù)所有的 key 重新計算對應下標播掷,存入到新的 table 中审编,這樣保證了后續(xù)查找的正確性。

到這里歧匈,HashMap put 相關(guān)的分析已經(jīng)完成垒酬,其中涉及到對數(shù)組和對應鏈表數(shù)據(jù)的插入。以及相應的數(shù)組進行擴容方案件炉。

HashMap 的 其他方法

分析完成了 put 方法之后勘究,再看其他方法就會很容易。

get 方法:

public V get(Object key) {
    if (key == null)
        return getForNullKey();
    Entry<K,V> entry = getEntry(key);

    return null == entry ? null : entry.getValue();
}

final Entry<K,V> getEntry(Object key) {
    if (size == 0) {
        return null;
    }

    int hash = (key == null) ? 0 : hash(key);// hash 處理
    for (Entry<K,V> e = table[indexFor(hash, table.length)];// 找到數(shù)組對應的下標
         e != null;
         e = e.next) {
        Object k;
        if (e.hash == hash &&
            ((k = e.key) == key || (key != null && key.equals(k))))
            return e;
    }
    return null;
}

get 方法需要傳入相應的 key 斟冕,如果為空 key 口糕,肯定就是去找 table 數(shù)組第 0 位的數(shù)據(jù)。否則磕蛇,key 的 hash 值獲取以及二次處理景描,找到對應的數(shù)組下標,比對對應 Entry 一致就返回秀撇,否則就返回 null超棺。

remove 方法:

public V remove(Object key) {
    Entry<K,V> e = removeEntryForKey(key);
    return (e == null ? null : e.value);
}

final Entry<K,V> removeEntryForKey(Object key) {
    if (size == 0) {
        return null;
    }
    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) // 刪除的是鏈表第一個數(shù)據(jù)
                table[i] = next;
            else
                prev.next = next;
            e.recordRemoval(this);
            return e;
        }
        prev = e;
        e = next;
    }

    return e;
}

remove 方法相對 get 方法,在拿到相應數(shù)據(jù)刪除之后呵燕,同時要將鏈表對應的數(shù)據(jù)重新進行指向棠绘。即將當前要刪除數(shù)據(jù)的前一項指向后一項。

HashMap 的遍歷方法

// 方式1
for(Map.Entry<String, Integer> entry : hhs.entrySet()){
    System.out.println("key = " + entry.getKey() + ", value = " + entry.getValue());
}

//方式2
Iterator<Map.Entry<String, Integer>> entries = hhs.entrySet().iterator();
while (entries.hasNext()) {
    Map.Entry<String, Integer> entry = entries.next();
    System.out.println("Key = " + entry.getKey() + ", Value = " + entry.getValue());
}

由上述 HashMap 的操作可以看到 HashMap 中存儲數(shù)據(jù)是無序的虏等,所以遍歷得到的數(shù)據(jù)也將會是無序的弄唧;通常 HashMap 使用到的兩種遍歷方式(其實兩種方法最終都會走向方式 2 ),entrySet 和 entrySet 的迭代器 iterator霍衫;

第一種遍歷方式候引;

private transient Set<Map.Entry<K,V>> entrySet = null;

public Set<Map.Entry<K,V>> entrySet() {
    return entrySet0();
}

private Set<Map.Entry<K,V>> entrySet0() {
    Set<Map.Entry<K,V>> es = entrySet;
    return es != null ? es : (entrySet = new EntrySet());
}

這里 entrySet 存儲 Entry 的集合,在之前 put 方法中敦跌,并沒有發(fā)現(xiàn)往其中存入數(shù)據(jù)澄干。所以逛揩,這里去實例化 EntrySet 方法中查看是否有相關(guān)獲取數(shù)據(jù)的方法?

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

EntrySet 的實例化又去創(chuàng)建了一個 EntryIterator麸俘;

Iterator<Map.Entry<K,V>> newEntryIterator()   {
    return new EntryIterator();
}

private final class EntryIterator extends HashIterator<Map.Entry<K,V>> {
    public Map.Entry<K,V> next() {
        return nextEntry();
    }
}

EntryIterator 實則就是一個 HashIterator辩稽,到這里可以發(fā)現(xiàn),似乎走到了第二種方式了从媚,繼續(xù)看 HashIterator逞泄;

private abstract class HashIterator<E> implements Iterator<E> {
    Entry<K,V> next;        // next entry to return
    int expectedModCount;   // For fast-fail
    int index;              // current slot
    Entry<K,V> current;     // current entry

    HashIterator() {
        expectedModCount = modCount;
        if (size > 0) { // advance to first entry
            Entry[] t = table;
            while (index < t.length && (next = t[index++]) == null) // 自增數(shù)組下標找到第一個不為null結(jié)束循環(huán) 即為HashMap要遍歷的第一個值
                ;
        }
    }

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

    final Entry<K,V> nextEntry() {
        if (modCount != expectedModCount)
            throw new ConcurrentModificationException();
        Entry<K,V> e = next;
        if (e == null)
            throw new NoSuchElementException();

        if ((next = e.next) == null) {// index 處鏈表數(shù)據(jù)沒有下一位了
            Entry[] t = table;
            while (index < t.length && (next = t[index++]) == null)
                ;
        }
        current = e;
        return e;
    }

    public void remove() {
        if (current == null)
            throw new IllegalStateException();
        if (modCount != expectedModCount)
            throw new ConcurrentModificationException();
        Object k = current.key;
        current = null;
        HashMap.this.removeEntryForKey(k);
        expectedModCount = modCount;
    }
}

HashIterator 實現(xiàn)了 Iterator,而增強 for 循環(huán)使用的就是 Iterator 拜效,所以這里 Entry 覆蓋了 Iterator 方法進行遍歷數(shù)據(jù)喷众。

HashIterator 構(gòu)造方法回去遍歷數(shù)組找到第一個不為 null 的數(shù)據(jù)結(jié)束循環(huán),此數(shù)據(jù)作為遍歷 HashMap 的起始數(shù)據(jù)紧憾;

這里主要看下 nextEntry 方法到千,因為 EntryIterator 的 next 方法其實調(diào)用的就是 nextEntry 方法;

可以看到 nextEntry 會將數(shù)據(jù)按照 table 的下標順序進行查找赴穗,如果某個下標對應的鏈表數(shù)據(jù)查找完畢憔四,繼續(xù)向下遍歷 table 數(shù)組。直到全部查找完畢般眉。

HashMap_put_04.png

以上圖 HashMap 數(shù)據(jù)來看了赵,最終遍歷出數(shù)據(jù)結(jié)果應該是(null,vaule)、(k9,v9)煤篙、(k3,v8)斟览、(k8,v8)、(k10,v10),當然辑奈,存儲順序大概率不是按照這個順序來的。所以我們說 HashMap 是無序的已烤。

最后鸠窗,可以看到 HashMap 也不支持多線程的操作 ,因為對 HashMap 的操作每次都會改變 modCount 值胯究,而遍歷的過程會比對相關(guān)值稍计,所以遍歷過程也不可對 HashMap 進行增刪操作。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末裕循,一起剝皮案震驚了整個濱河市臣嚣,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌剥哑,老刑警劉巖硅则,帶你破解...
    沈念sama閱讀 206,839評論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異株婴,居然都是意外死亡怎虫,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,543評論 2 382
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來大审,“玉大人蘸际,你說我怎么就攤上這事⊥椒觯” “怎么了粮彤?”我有些...
    開封第一講書人閱讀 153,116評論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長姜骡。 經(jīng)常有香客問我导坟,道長,這世上最難降的妖魔是什么溶浴? 我笑而不...
    開封第一講書人閱讀 55,371評論 1 279
  • 正文 為了忘掉前任乍迄,我火速辦了婚禮,結(jié)果婚禮上士败,老公的妹妹穿的比我還像新娘闯两。我一直安慰自己,他們只是感情好谅将,可當我...
    茶點故事閱讀 64,384評論 5 374
  • 文/花漫 我一把揭開白布漾狼。 她就那樣靜靜地躺著,像睡著了一般饥臂。 火紅的嫁衣襯著肌膚如雪逊躁。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,111評論 1 285
  • 那天隅熙,我揣著相機與錄音稽煤,去河邊找鬼。 笑死囚戚,一個胖子當著我的面吹牛酵熙,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播驰坊,決...
    沈念sama閱讀 38,416評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼匾二,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了拳芙?” 一聲冷哼從身側(cè)響起察藐,我...
    開封第一講書人閱讀 37,053評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎舟扎,沒想到半個月后分飞,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,558評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡浆竭,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,007評論 2 325
  • 正文 我和宋清朗相戀三年浸须,在試婚紗的時候發(fā)現(xiàn)自己被綠了惨寿。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,117評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡删窒,死狀恐怖裂垦,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情肌索,我是刑警寧澤蕉拢,帶...
    沈念sama閱讀 33,756評論 4 324
  • 正文 年R本政府宣布,位于F島的核電站诚亚,受9級特大地震影響晕换,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜站宗,卻給世界環(huán)境...
    茶點故事閱讀 39,324評論 3 307
  • 文/蒙蒙 一闸准、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧梢灭,春花似錦夷家、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,315評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至钥顽,卻和暖如春义屏,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背蜂大。 一陣腳步聲響...
    開封第一講書人閱讀 31,539評論 1 262
  • 我被黑心中介騙來泰國打工闽铐, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人奶浦。 一個月前我還...
    沈念sama閱讀 45,578評論 2 355
  • 正文 我出身青樓阳啥,卻偏偏與公主長得像,于是被迫代替她去往敵國和親财喳。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 42,877評論 2 345

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