HashMap源碼分析(JDK1.8版本)

基本概念

HashMap又叫哈希表、散列表省店,是一種以key/value方式存儲數(shù)據(jù)的數(shù)據(jù)結(jié)構(gòu)嚣崭,它利用不重復(fù)、無序的鍵實(shí)現(xiàn)了快速查找懦傍。每個key對應(yīng)唯一的value雹舀,查詢和修改的效率都很快,能達(dá)到O(1)的平均時間復(fù)雜度谎脯。它是非線程安全的葱跋,且不保證元素存儲的順序持寄。


繼承關(guān)系

Collection家族關(guān)系

public class HashMap<K,V> extends AbstractMap<K,V>
    implements Map<K,V>, Cloneable, Serializable {
  • 繼承自AbstractMap,并實(shí)現(xiàn)Map,具備Map的所有功能
  • 實(shí)現(xiàn)了Cloneable源梭,可以被復(fù)制,原型設(shè)計(jì)模式
  • 實(shí)現(xiàn)了Serializable,可以被序列化

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

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

  • 藍(lán)色部分是數(shù)組稍味,數(shù)組的每一個元素也叫做桶废麻,但插入元素和已有元素發(fā)生Hash碰撞時,這兩個Hash碰撞的元素將會以單鏈表方式存放在同一個桶里模庐。
  • JDK1.8版本之前,采用數(shù)組+鏈表的方式,當(dāng)發(fā)生Hash碰撞的次數(shù)足夠多時际跪,桶內(nèi)的單鏈表將會變的非常長春感,查找元素的時間復(fù)雜度將會退化成O(n)。為了解決這個問題疼燥,在JDK1.8之后沧卢,引入了紅黑樹,在桶里單鏈表長度>=8時醉者,單鏈表將會轉(zhuǎn)換成紅黑樹,查找時間復(fù)雜度從O(n)降低到O(log n),極大的提升了查找效率但狭。在桶內(nèi)單鏈表長度<=6,紅黑樹轉(zhuǎn)換回單鏈表的方式存儲。

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

  • 無參構(gòu)造
    static final float DEFAULT_LOAD_FACTOR = 0.75f;
    public HashMap() {
        this.loadFactor = DEFAULT_LOAD_FACTOR; 
    }

無參構(gòu)造擴(kuò)容因子默認(rèn)值0.75f

  • int參數(shù)構(gòu)造
    public HashMap(int initialCapacity) {
        this(initialCapacity, 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;
        this.threshold = tableSizeFor(initialCapacity);
    }

    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;
    }
  • 傳入initialCapacity初始容量撬即,將initialCapacity和默認(rèn)擴(kuò)容因子傳入調(diào)用自身HashMap(int initialCapacity, float loadFactor)構(gòu)造方法
  • HashMap(int initialCapacity, float loadFactor)判斷傳入的初始容量initialCapacity是否合法立磁,必須大于0。
  • tableSizeFor(initialCapacity)方法計(jì)算是否需要擴(kuò)容的閥值剥槐,并賦值給threshold,
    tableSizeFor(int cap)保證擴(kuò)容閥值為2的冪次唱歧,接下來重點(diǎn)看下這個方法是怎么保證為2的冪次的。
舉個例子
假如tableSizeFor(int cap)方法傳入的參數(shù)是10粒竖,
int n = cap - 1; 
//n=9 轉(zhuǎn)二進(jìn)制 1001 補(bǔ)足8位= 0000 1001
n |= n >>> 1;
// 9|9無符號右移1位 = 0000 1001|0000 0100 = 0000 1101
n |= n >>> 2;
//0000 1101|0000 0011 = 0000 1111 //后面的值不再有變化颅崩,都是一樣
n |= n >>> 4;
// 0000 1111|0000 0000 = 0000 1111
n |= n >>> 8;
//0000 1111
n |= n >>> 16;
//0000 1111
return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
//后面return的值等于 0000 1111 + 1 = 0001 0000 再轉(zhuǎn)10進(jìn)制 = 2的4次方 =16

再解釋一下為什么需要int n = cap - 1,為了避免參數(shù)cap本來就是2的冪次方温圆,經(jīng)過后續(xù)的未操作的挨摸,cap將會變成2 * cap,不符合預(yù)期。

為什么擴(kuò)容閥值threshold要保證為2的冪次岁歉?

threshold要保證為2的冪次方主要原因是HashMap中數(shù)據(jù)存儲有關(guān),HashMap中key的Hash值由hash(Object key)方法計(jì)算得來得运。

    static final int hash(Object key) {
        int h;
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
    }
  • HashMap中存儲數(shù)據(jù)table的index是由key的Hash值決定的膝蜈。
  • 在HashMap存儲數(shù)據(jù)的時候,我們期望數(shù)據(jù)能夠均勻分布熔掺,以避免哈希沖突饱搏。我們首先可能會想到采用%取余的操作來實(shí)現(xiàn)。
  • 取余(%)操作中如果除數(shù)是2的冪次則等價于與其除數(shù)減一的與(&)操作(也就是說 hash%length==hash&(length-1)的前提是 length 是2的冪次方置逻。
  • 采用二進(jìn)制位操作 &推沸,相對于%能夠提高運(yùn)算效率,這就是HashMap的長度為什么是2的冪次方的原因券坞。
  • Map參數(shù)構(gòu)造
    public HashMap(Map<? extends K, ? extends V> m) {
        this.loadFactor = DEFAULT_LOAD_FACTOR;
        putMapEntries(m, false);
    }

    final void putMapEntries(Map<? extends K, ? extends V> m, boolean evict) {
        int s = m.size();
        if (s > 0) {
            if (table == null) {
                float ft = ((float)s / loadFactor) + 1.0F;
                int t = ((ft < (float)MAXIMUM_CAPACITY) ?
                         (int)ft : MAXIMUM_CAPACITY);
                if (t > threshold)
                    threshold = tableSizeFor(t);
            }
            else if (s > threshold)
                resize();
            for (Map.Entry<? extends K, ? extends V> e : m.entrySet()) {
                K key = e.getKey();
                V value = e.getValue();
                putVal(hash(key), key, value, false, evict);
            }
        }
    }
  • 擴(kuò)容因子默認(rèn)值0.75f
  • putMapEntries方法將傳入的Map或Map子類傳入放入自身進(jìn)行存儲

put(K key, V value)方法

    
  public V put(K key, V value) {
      return putVal(hash(key), key, value, false,  true);
  }

put調(diào)用了內(nèi)部的putVal(hash(key), key, value, false, true)

@param hash -> key.hashCode())^ (h >>> 16) 這里可以認(rèn)為是key的hash值
@param key
@param value
@param onlyIfAbsent 是否不改變現(xiàn)有的值
@param evict 如果為false鬓催,則表處于創(chuàng)建模式。
@return previous value, or null if none

    final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {
        Node<K,V>[] tab; Node<K,V> p; int n, i;
        if ((tab = table) == null || (n = tab.length) == 0)
            n = (tab = resize()).length;//如果是第一次put恨锚,初始化tab
        if ((p = tab[i = (n - 1) & hash]) == null)
            // 通過hash計(jì)算當(dāng)前的下標(biāo)索引對應(yīng)的value為null宇驾,創(chuàng)建新的鏈表節(jié)點(diǎn)賦值
            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))))
                // 如果key相同,p賦值給e
                e = p;
            else if (p instanceof TreeNode)
                // 如果p是紅黑樹類型,調(diào)用putTreeVal方式賦值
                e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
            else {
                //單鏈表
                for (int binCount = 0; ; ++binCount) {
                    if ((e = p.next) == null) {
                        // 如果p的next為空猴伶,將新的value值添加至鏈表后面
                        p.next = newNode(hash, key, value, null);
                        if (binCount >= TREEIFY_THRESHOLD - 1) // 循環(huán)初始值0 课舍,所以-1
                             //static final int TREEIFY_THRESHOLD = 8;
                            // 如果鏈表長度大于等于8,鏈表轉(zhuǎn)化為紅黑樹他挎,執(zhí)行插入
                            treeifyBin(tab, hash);
                        break;
                    }
                    // key相同則跳出循環(huán)
                    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;
               //已存在相同的key筝尾,覆蓋并返回舊的value
                if (!onlyIfAbsent || oldValue == null)
                    e.value = value;
                afterNodeAccess(e);//給子類的回調(diào)方法
                return oldValue;
            }
        }
        ++modCount;
        if (++size > threshold)
            // size大于加載因子,擴(kuò)容
            resize();
        afterNodeInsertion(evict);//給子類的回調(diào)方法
        return null;
    }

擴(kuò)容resize()方法

    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) {//舊表為空,不是第一次put
            // 超過容量最大值就不再擴(kuò)容了办桨,就只好返回舊表 基本不可能發(fā)生
            if (oldCap >= MAXIMUM_CAPACITY) {
                threshold = Integer.MAX_VALUE;
                return oldTab; 
            }
            // 沒超過最大值筹淫,擴(kuò)容oldThr << 1,即原來的2倍
            else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
                     oldCap >= DEFAULT_INITIAL_CAPACITY)
                newThr = oldThr << 1; 
        }
        else if (oldThr > 0) 
            newCap = oldThr;// 新的容量大小 = 舊擴(kuò)容閥值
        else {               // zero initial threshold signifies using defaults
            newCap = DEFAULT_INITIAL_CAPACITY;
            newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
        }
      // 計(jì)算新的擴(kuò)容閥值 容量*擴(kuò)容因子 默認(rèn)擴(kuò)容因子0.75
        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) {
            // 把每個數(shù)組元素(桶)都移動到新的桶中
            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 { // 保證順序
                        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;
                            }
                            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;
                        }
                        if (hiTail != null) {
                            // 原索引+oldCap放到bucket里
                            hiTail.next = null;
                            newTab[j + oldCap] = hiHead;
                        }
                    }
                }
            }
        }
        return newTab;
    }

get(Object key)方法

    public V get(Object key) {
        Node<K,V> e;
        return (e = getNode(hash(key), key)) == null ? null : e.value;
    }
  • 調(diào)用tab[(n - 1) & hash]方法獲取到key的hash對應(yīng)的數(shù)組元素(桶)
  • 調(diào)用getNode()方法通過key和hash獲取對應(yīng)的value崔挖。不存在則返回null
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) {
                if (first instanceof TreeNode)
                    return ((TreeNode<K,V>)first).getTreeNode(hash, key);
                do {
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        return e;
                } while ((e = e.next) != null);
            }
        }
        return null;
    }
  • 1.若數(shù)組table不為null且長度大于0且其索引為(n - 1) & hash(等價于hash%n)的節(jié)點(diǎn)不為null贸街。其中n為數(shù)組長度,hash為插入的鍵值對的key的哈希值狸相。則進(jìn)入下一步薛匪,否則直接返回null
  • 2.判斷首節(jié)點(diǎn)的key和hash是否與入?yún)⒁恢拢粝嗤瑒t返回首節(jié)點(diǎn)脓鹃,否則進(jìn)入下一步逸尖。
  • 3.判斷節(jié)點(diǎn)個數(shù)只有1個,若是則返回null瘸右,否則進(jìn)入下一步
  • 4.判斷首節(jié)點(diǎn)是否為樹節(jié)點(diǎn)娇跟,若是則遍歷紅黑樹,否則為鏈表太颤,進(jìn)入下一步
  • 5.遍歷鏈表苞俘,檢索key和hash與入?yún)⑾嗤墓?jié)點(diǎn),若找到則返回該節(jié)點(diǎn)龄章,否則返回null

remove(Object key)/remove(Object key, Object value)方法

    public V remove(Object key) {
        Node<K,V> e;
        return (e = removeNode(hash(key), key, null, false, true)) == null ?
            null : e.value;
    }

    public boolean remove(Object key, Object value) {
        return removeNode(hash(key), key, value, true, true) != null;
    }

remove調(diào)用removeNode方法

@param hash -> key.hashCode())^ (h >>> 16) 這里可以認(rèn)為是key的hash值
@param key
@param value 要刪除的鍵值對的value吃谣,該值是否作為刪除的條件取決于matchValue是否為true
@param matchValue 如果為true乞封,則當(dāng)key對應(yīng)的鍵值對的值equals(value)為true時才刪除;否則不關(guān)心value的值
@param movable 刪除后是否移動節(jié)點(diǎn)岗憋,如果為false肃晚,則不移動
@return 返回節(jié)點(diǎn),如果沒有仔戈,則返回null


final Node<K,V> removeNode(int hash, Object key, Object value,
                            boolean matchValue, boolean movable) {
    Node<K,V>[] tab; Node<K,V> p; int n, index; // 聲明節(jié)點(diǎn)數(shù)組关串、當(dāng)前節(jié)點(diǎn)、數(shù)組長度监徘、索引值
    /*
     * 如果 節(jié)點(diǎn)數(shù)組tab不為空晋修、數(shù)組長度n大于0、根據(jù)hash定位到的節(jié)點(diǎn)對象p(該節(jié)點(diǎn)為 樹的根節(jié)點(diǎn) 或 鏈表的首節(jié)點(diǎn))不為空
     * 需要從該節(jié)點(diǎn)p向下遍歷耐量,找到那個和key匹配的節(jié)點(diǎn)對象
     */
    if ((tab = table) != null && (n = tab.length) > 0 &&
        (p = tab[index = (n - 1) & hash]) != null) {
        Node<K,V> node = null, e; K k; V v; // 定義要返回的節(jié)點(diǎn)對象飞蚓,聲明一個臨時節(jié)點(diǎn)變量滤港、鍵變量廊蜒、值變量
 
        // 如果當(dāng)前節(jié)點(diǎn)的鍵和key相等,那么當(dāng)前節(jié)點(diǎn)就是要刪除的節(jié)點(diǎn)溅漾,賦值給node
        if (p.hash == hash &&
            ((k = p.key) == key || (key != null && key.equals(k))))
            node = p;
 
        /*
         * 到這一步說明首節(jié)點(diǎn)沒有匹配上山叮,那么檢查下是否有next節(jié)點(diǎn)
         * 如果沒有next節(jié)點(diǎn),就說明該節(jié)點(diǎn)所在位置上沒有發(fā)生hash碰撞, 就一個節(jié)點(diǎn)并且還沒匹配上添履,也就沒得刪了屁倔,最終也就返回null了
         * 如果存在next節(jié)點(diǎn),就說明該數(shù)組位置上發(fā)生了hash碰撞暮胧,此時可能存在一個鏈表锐借,也可能是一顆紅黑樹
         */
        else if ((e = p.next) != null) {
            // 如果當(dāng)前節(jié)點(diǎn)是TreeNode類型,說明已經(jīng)是一個紅黑樹往衷,那么調(diào)用getTreeNode方法從樹結(jié)構(gòu)中查找滿足條件的節(jié)點(diǎn)
            if (p instanceof TreeNode)
                node = ((TreeNode<K,V>)p).getTreeNode(hash, key);
            // 如果不是樹節(jié)點(diǎn)钞翔,那么就是一個鏈表,只需要從頭到尾逐個節(jié)點(diǎn)比對即可    
            else {
                do {
                    // 如果e節(jié)點(diǎn)的鍵是否和key相等席舍,e節(jié)點(diǎn)就是要刪除的節(jié)點(diǎn)布轿,賦值給node變量,調(diào)出循環(huán)
                    if (e.hash == hash &&
                        ((k = e.key) == key ||
                            (key != null && key.equals(k)))) {
                        node = e;
                        break;
                    }
 
                    // 走到這里来颤,說明e也沒有匹配上
                    p = e; // 把當(dāng)前節(jié)點(diǎn)p指向e汰扭,這一步是讓p存儲的永遠(yuǎn)下一次循環(huán)里e的父節(jié)點(diǎn),如果下一次e匹配上了福铅,那么p就是node的父節(jié)點(diǎn)
                } while ((e = e.next) != null); // 如果e存在下一個節(jié)點(diǎn)萝毛,那么繼續(xù)去匹配下一個節(jié)點(diǎn)。直到匹配到某個節(jié)點(diǎn)跳出 或者 遍歷完鏈表所有節(jié)點(diǎn)
            }
        }
 
        /*
         * 如果node不為空滑黔,說明根據(jù)key匹配到了要刪除的節(jié)點(diǎn)
         * 如果不需要對比value值  或者  需要對比value值但是value值也相等
         * 那么就可以刪除該node節(jié)點(diǎn)了
         */
        if (node != null && (!matchValue || (v = node.value) == value ||
                                (value != null && value.equals(v)))) {
            if (node instanceof TreeNode) // 如果該節(jié)點(diǎn)是個TreeNode對象笆包,說明此節(jié)點(diǎn)存在于紅黑樹結(jié)構(gòu)中鲁冯,調(diào)用removeTreeNode方法(該方法單獨(dú)解析)移除該節(jié)點(diǎn)
                ((TreeNode<K,V>)node).removeTreeNode(this, tab, movable);
            else if (node == p) // 如果該節(jié)點(diǎn)不是TreeNode對象,node == p 的意思是該node節(jié)點(diǎn)就是首節(jié)點(diǎn)
                tab[index] = node.next; // 由于刪除的是首節(jié)點(diǎn)色查,那么直接將節(jié)點(diǎn)數(shù)組對應(yīng)位置指向到第二個節(jié)點(diǎn)即可
            else // 如果node節(jié)點(diǎn)不是首節(jié)點(diǎn)薯演,此時p是node的父節(jié)點(diǎn),由于要刪除node秧了,所有只需要把p的下一個節(jié)點(diǎn)指向到node的下一個節(jié)點(diǎn)即可把node從鏈表中刪除了
                p.next = node.next;
            ++modCount; // HashMap的修改次數(shù)遞增
            --size; // HashMap的元素個數(shù)遞減
            afterNodeRemoval(node); // 調(diào)用afterNodeRemoval方法跨扮,該方法HashMap沒有任何實(shí)現(xiàn)邏輯,目的是為了讓子類根據(jù)需要自行覆寫
            return node;
        }
    }
    return null;
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末验毡,一起剝皮案震驚了整個濱河市衡创,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌晶通,老刑警劉巖璃氢,帶你破解...
    沈念sama閱讀 212,383評論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異狮辽,居然都是意外死亡一也,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,522評論 3 385
  • 文/潘曉璐 我一進(jìn)店門喉脖,熙熙樓的掌柜王于貴愁眉苦臉地迎上來椰苟,“玉大人,你說我怎么就攤上這事树叽∮吆” “怎么了?”我有些...
    開封第一講書人閱讀 157,852評論 0 348
  • 文/不壞的土叔 我叫張陵题诵,是天一觀的道長洁仗。 經(jīng)常有香客問我,道長性锭,這世上最難降的妖魔是什么赠潦? 我笑而不...
    開封第一講書人閱讀 56,621評論 1 284
  • 正文 為了忘掉前任,我火速辦了婚禮篷店,結(jié)果婚禮上祭椰,老公的妹妹穿的比我還像新娘。我一直安慰自己疲陕,他們只是感情好方淤,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,741評論 6 386
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著蹄殃,像睡著了一般携茂。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上诅岩,一...
    開封第一講書人閱讀 49,929評論 1 290
  • 那天讳苦,我揣著相機(jī)與錄音带膜,去河邊找鬼。 笑死鸳谜,一個胖子當(dāng)著我的面吹牛膝藕,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播咐扭,決...
    沈念sama閱讀 39,076評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼芭挽,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了蝗肪?” 一聲冷哼從身側(cè)響起袜爪,我...
    開封第一講書人閱讀 37,803評論 0 268
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎薛闪,沒想到半個月后辛馆,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,265評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡豁延,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,582評論 2 327
  • 正文 我和宋清朗相戀三年昙篙,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片术浪。...
    茶點(diǎn)故事閱讀 38,716評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡瓢对,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出胰苏,到底是詐尸還是另有隱情,我是刑警寧澤醇疼,帶...
    沈念sama閱讀 34,395評論 4 333
  • 正文 年R本政府宣布硕并,位于F島的核電站,受9級特大地震影響秧荆,放射性物質(zhì)發(fā)生泄漏倔毙。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,039評論 3 316
  • 文/蒙蒙 一乙濒、第九天 我趴在偏房一處隱蔽的房頂上張望陕赃。 院中可真熱鬧,春花似錦颁股、人聲如沸么库。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,798評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽诉儒。三九已至,卻和暖如春亏掀,著一層夾襖步出監(jiān)牢的瞬間忱反,已是汗流浹背泛释。 一陣腳步聲響...
    開封第一講書人閱讀 32,027評論 1 266
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留温算,地道東北人怜校。 一個月前我還...
    沈念sama閱讀 46,488評論 2 361
  • 正文 我出身青樓,卻偏偏與公主長得像注竿,于是被迫代替她去往敵國和親韭畸。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,612評論 2 350