最通俗易懂的 HashMap 源碼分析解讀

HashMap 作為最常用的集合類之一滤淳,有必要深入淺出的了解一下贤徒。這篇文章會(huì)深入到 HashMap 源碼醉蚁,刨析它的存儲(chǔ)結(jié)構(gòu)以及工作機(jī)制睛琳。

1. HashMap 的存儲(chǔ)結(jié)構(gòu)

HashMap 的數(shù)據(jù)存儲(chǔ)結(jié)構(gòu)是一個(gè) Node<K,V> 數(shù)組盒蟆,在(Java 7 中是 Entry<K,V> 數(shù)組,但結(jié)構(gòu)相同)

public class HashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>, Cloneable, Serializable {
    // 數(shù)組
    transient Node<K,V>[] table;    
    static class Node<K,V> implements Map.Entry<K,V> {
        final int hash;
        final K key;
        V value;
        // 鏈表
        Node<K,V> next;
        ....
    }
    .....
}

存儲(chǔ)結(jié)構(gòu)主要是****數(shù)組加鏈表****师骗,像下面的圖历等。

HashMap 存儲(chǔ)結(jié)構(gòu)(圖片來(lái)自網(wǎng)絡(luò))

2. HashMap 的 put()

在 Java 8 中 HashMap 的 put 方法如下,我已經(jīng)詳細(xì)注釋了重要代碼辟癌。

public V put(K key, V value) {
    return putVal(hash(key), key, value, false, true);
}
// 計(jì)算哈希值 與(&)寒屯、非(~)、或(|)黍少、異或(^)
static final int hash(Object key) {
    int h;
    return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) {
   
    Node<K,V>[] tab; Node<K,V> p; int n, i;
    // 如果數(shù)組為空寡夹,進(jìn)行 resize() 初始化
    if ((tab = table) == null || (n = tab.length) == 0)
        n = (tab = resize()).length;
    // 如果計(jì)算的位置上Node不存在,直接創(chuàng)建節(jié)點(diǎn)插入
    if ((p = tab[i = (n - 1) & hash]) == null)
        tab[i] = newNode(hash, key, value, null);
    else {
        // 如果計(jì)算的位置上Node 存在厂置,鏈表處理
        Node<K,V> e; K k;
        // 如果 hash 值菩掏,k 值完全相同,直接覆蓋
        if (p.hash == hash &&((k = p.key) == key || (key != null && key.equals(k))))
            e = p;
        // 如果 index 位置元素已經(jīng)存在昵济,且是紅黑樹(shù)
        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) {
                    // 找到節(jié)點(diǎn)鏈表中next為空的節(jié)點(diǎn)智绸,創(chuàng)建新的節(jié)點(diǎn)插入
                    p.next = newNode(hash, key, value, null);
                    // 如果節(jié)點(diǎn)鏈表中數(shù)量超過(guò)TREEIFY_THRESHOLD(8)個(gè),轉(zhuǎn)化為紅黑樹(shù)
                    if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                        treeifyBin(tab, hash);
                    break;
                }
                // 如果節(jié)點(diǎn)鏈表中有發(fā)現(xiàn)已有相同key
                if (e.hash == hash &&
                    ((k = e.key) == key || (key != null && key.equals(k))))
                    break;
                p = e;
            }
        }
        // 如果節(jié)點(diǎn) e 有值访忿,放入數(shù)組 table[]
        if (e != null) { // existing mapping for key
            V oldValue = e.value;
            if (!onlyIfAbsent || oldValue == null)
                e.value = value;
            afterNodeAccess(e);
            return oldValue;
        }
    }
 
    ++modCount;
    // 當(dāng)前大小大于臨界大小瞧栗,擴(kuò)容
    if (++size > threshold)
        resize();
    afterNodeInsertion(evict);
    return null;
}

舉個(gè)例子,如果 put 的 key 為字母 a醉顽,當(dāng)前 HashMap 容量是初始容量 16沼溜,計(jì)算出位置是 1平挑。

# int hash = key.hashCode()
# hash = hash ^ (hash >>> 16)
# 公式 index = (n - 1) & hash // n 是容量
    
hash HEX(97)  = 0110 0001
n-1  HEX(15)  = 0000 1111
--------------------------
         結(jié)果  = 0000 0001
# 計(jì)算得到位置是 1

總結(jié) HashMap put 過(guò)程游添。

  1. 計(jì)算 key 的 hash 值系草。

    計(jì)算方式是 (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);

  2. 檢查當(dāng)前數(shù)組是否為空,為空需要進(jìn)行初始化唆涝,初始化容量是 16 找都,負(fù)載因子默認(rèn) 0.75

  3. 計(jì)算 key 在數(shù)組中的坐標(biāo)廊酣。

    計(jì)算方式:(容量 - 1) & hash.

    因?yàn)槿萘靠偸?的次方能耻,所以-1的值的二進(jìn)制總是全1。方便與 hash 值進(jìn)行運(yùn)算亡驰。

  4. 如果計(jì)算出的坐標(biāo)元素為空晓猛,創(chuàng)建節(jié)點(diǎn)加入,put 結(jié)束凡辱。

    1. 如果當(dāng)前數(shù)組容量大于負(fù)載因子設(shè)置的容量戒职,進(jìn)行擴(kuò)容
  5. 如果計(jì)算出的坐標(biāo)元素有值透乾。

    1. 如果坐標(biāo)上的元素值和要加入的值 key 完全一樣洪燥,覆蓋原有值。

    2. 如果坐標(biāo)上的元素是紅黑樹(shù)乳乌,把要加入的值和 key 加入到紅黑樹(shù)捧韵。

    3. 如果坐標(biāo)上的元素和要加入的元素不同(尾插法增加)。

      1. 如果 next 節(jié)點(diǎn)為空汉操,把要加入的值和 key 加入 next 節(jié)點(diǎn)再来。

      2. 如果 next 節(jié)點(diǎn)不為空,循環(huán)查看 next 節(jié)點(diǎn)磷瘤。

        如果發(fā)現(xiàn)有 next 節(jié)點(diǎn)的 key 和要加入的 key 一樣其弊,對(duì)應(yīng)的值替換為新值。

      3. 如果循環(huán) next 節(jié)點(diǎn)查找超過(guò)8層還不為空膀斋,把這個(gè)位置元素轉(zhuǎn)換為紅黑樹(shù)梭伐。

3. HashMap 的 get()

在 Java 8 中 get 方法源碼如下,我已經(jīng)做了注釋說(shuō)明仰担。

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;
    // 只有在存儲(chǔ)數(shù)組已經(jīng)存在的情況下進(jìn)入這個(gè) if
    if ((tab = table) != null && (n = tab.length) > 0 &&
        (first = tab[(n - 1) & hash]) != null) {
        // first 是獲取的坐標(biāo)上元素
        if (first.hash == hash && // always check first node
            ((k = first.key) == key || (key != null && key.equals(k))))
            // key 相同糊识,說(shuō)明first是想要的元素,返回
            return first;
        if ((e = first.next) != null) {
            if (first instanceof TreeNode)
                // 如果是紅黑樹(shù)摔蓝,從紅黑樹(shù)中查找結(jié)果
                return ((TreeNode<K,V>)first).getTreeNode(hash, key);
            do {
                // 循環(huán)遍歷查找
                if (e.hash == hash &&
                    ((k = e.key) == key || (key != null && key.equals(k))))
                    return e;
            } while ((e = e.next) != null);
        }
    }
    return null;
 }

get 方法流程總結(jié)赂苗。

  1. 計(jì)算 key 的 hash 值。
  2. 如果存儲(chǔ)數(shù)組不為空贮尉,且計(jì)算得到的位置上的元素不為空拌滋。繼續(xù),否則猜谚,返回 Null败砂。
  3. 如果獲取到的元素的 key 值相等,說(shuō)明查找到了昌犹,返回元素。
  4. 如果獲取到的元素的 key 值不相等斜姥,查找 next 節(jié)點(diǎn)的元素。
    1. 如果元素是紅黑樹(shù)铸敏,在紅黑樹(shù)中查找。
    2. 不是紅黑樹(shù)杈笔,遍歷 next 節(jié)點(diǎn)查找,找到則返回桩撮。

4. HashMap 的 Hash 規(guī)則

  1. 計(jì)算 hash 值 int hash = key.hashCode()。
  2. 與或上 hash 值無(wú)符號(hào)右移16 位店量。 hash = hash ^ (hash >>> 16)。
  3. 位置計(jì)算公式 index = (n - 1) & hash 融师,其中 n 是容量。

這里可能會(huì)有同學(xué)對(duì) hash ^ (hash >>> 16) 有疑惑旱爆,很好奇為什么這里要拿 hash 值異或上 hash 值無(wú)符號(hào)右移 16 位呢舀射?下面通過(guò)一個(gè)例子演示其中道理所在。

假設(shè) hash 值是 0001 0100 1100 0010 0110 0001 0010 0000怀伦,當(dāng)前容量是 16脆烟。

hash        = 0001 0100 1100 0010 0110 0001 0010 0000  ---
                                                         | 與或計(jì)算
hash >>> 16 = 0000 0000 0000 0000 0001 0100 1100 0010  ---
------------------------------------------------------ 
hash 結(jié)果    = 0001 0100 1100 0010 0111 0101 1110 0100 ---
                                                         | & 與運(yùn)算
容量 -1      = 0000 0000 0000 0000 0000 0000 0000 1111 ---
------------------------------------------------------
# 得到位置    = 0000 0000 0000 0000 0000 0000 0000 0100 得到位置是 4

如果又新增一個(gè)數(shù)據(jù),得到 hash 值是 0100 0000 1110 0010 1010 0010 0001 0000 房待,容量還是16邢羔,計(jì)算他的位置應(yīng)該是什么呢?

hash        = 0100 0000 1110 0010 1010 0010 0001 0000  ---
                                                         | 與或計(jì)算
hash >>> 16 = 0000 0000 0000 0000 0001 0100 1100 0010  ---
------------------------------------------------------ 
hash 結(jié)果    = 0100 0000 1110 0010 1011 0110 1101 0010 ---
                                                         | & 與運(yùn)算
容量 -1      = 0000 0000 0000 0000 0000 0000 0000 1111 ---
------------------------------------------------------
# 得到位置    = 0000 0000 0000 0000 0000 0000 0000 0010 得到位置是 2

上面兩個(gè)例子桑孩,得到位置一個(gè)是 4拜鹤,一個(gè)是 2,上面只是我隨便輸入的兩個(gè)二進(jìn)制數(shù)流椒,那么這兩個(gè)數(shù)如果不經(jīng)過(guò) hash ^ (hash >>> 16) 運(yùn)算敏簿,位置會(huì)有什么變化呢?

hash        = 0001 0100 1100 0010 0110 0001 0010 0000
容量 -1      = 0000 0000 0000 0000 0000 0000 0000 1111
------------------------------------------------------
        結(jié)果 = 0000 0000 0000 0000 0000 0000 0000 0000
# 得到位置是 0 
hash        = 0100 0000 1110 0010 1010 0010 0001 0000
容量 -1      = 0000 0000 0000 0000 0000 0000 0000 1111
------------------------------------------------------
        結(jié)果 = 0000 0000 0000 0000 0000 0000 0000 0000
# 得到位置是 0 

可以發(fā)現(xiàn)位置都是 0 ,沖突概率提高了惯裕∥率可見(jiàn) hash ^ (hash >>> 16) 讓數(shù)據(jù)的 hash 值的高 16 位與低 16 位進(jìn)行與或混合,可以減少低位相同時(shí)數(shù)據(jù)插入沖突的概率轻猖。

5. HashMap 的初始化大小

  1. 初始化大小是 16帆吻,為什么是 16 呢域那?

    這可能是因?yàn)槊看螖U(kuò)容都是 2 倍咙边。而選擇 2 的次方值 16 作為初始容量,有利于擴(kuò)容時(shí)重新 Hash 計(jì)算位置次员。為什么是 16 我想是一個(gè)經(jīng)驗(yàn)值败许,理論上說(shuō)只要是 2 的次方都沒(méi)有問(wèn)題。

6. HashMap 的擴(kuò)容方式

負(fù)載因子是多少淑蔚?負(fù)載因子是 0.75市殷。

擴(kuò)容方式是什么?看源碼說(shuō)明刹衫。

  /**
     * 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.
     *
     * @return the table
     */
    final Node<K,V>[] resize() {
        Node<K,V>[] oldTab = table;
        // 現(xiàn)有容量
        int oldCap = (oldTab == null) ? 0 : oldTab.length;
        // 現(xiàn)有擴(kuò)容閥值
        int oldThr = threshold;
        int newCap, newThr = 0;
        if (oldCap > 0) {
            // 如果當(dāng)前長(zhǎng)度已經(jīng)大于最大容量醋寝。結(jié)束擴(kuò)容
            if (oldCap >= MAXIMUM_CAPACITY) {
                threshold = Integer.MAX_VALUE;
                return oldTab;
            }
            // 如果擴(kuò)大兩倍之后小于最大容量,且現(xiàn)有容量大于等于初始容量音羞,就擴(kuò)大兩倍
            else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY && oldCap >= DEFAULT_INITIAL_CAPACITY)
               // 擴(kuò)容閥值擴(kuò)大為兩倍
                newThr = oldThr << 1; // double threshold
        }
        // 當(dāng)前容量 = 0 嗅绰,但是當(dāng)前記錄容量 > 0 搀继,獲取當(dāng)前記錄容量叽躯。
       else if (oldThr > 0) // initial capacity was placed in threshold
            // 進(jìn)入這里,說(shuō)明是通過(guò)指定容量和負(fù)載因子的構(gòu)造函數(shù)
            newCap = oldThr;
        else {                 // zero initial threshold signifies using defaults
            // 進(jìn)入這里說(shuō)明是通過(guò)無(wú)參構(gòu)造
            // 新的容量
            newCap = DEFAULT_INITIAL_CAPACITY;
            // 新的閥值
            newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
        }
        if (newThr == 0) {
            float ft = (float)newCap * loadFactor;
            // 計(jì)算擴(kuò)容閥值
            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;
        // 如果 oldTab != null制圈,說(shuō)明是擴(kuò)容鲸鹦,否則是初始化馋嗜,直接返回
        if (oldTab != null) {
            for (int j = 0; j < oldCap; ++j) {
                Node<K,V> e;
                if ((e = oldTab[j]) != null) {
                    oldTab[j] = null;
                    // 如果當(dāng)前元素 next節(jié)點(diǎn)沒(méi)有元素葛菇,當(dāng)前元素重新計(jì)算位置直接放入
                    if (e.next == null)
                        newTab[e.hash & (newCap - 1)] = e;
                    else if (e instanceof TreeNode)
                        // 如果當(dāng)前節(jié)點(diǎn)是紅黑樹(shù)
                        ((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;
                            // == 0 ,位置不變
                            if ((e.hash & oldCap) == 0) {
                                if (loTail == null)
                                    loHead = e;
                                else
                                    loTail.next = e;
                                loTail = e;
                            }
                            // e.hash & oldCap != 0 ,位置變?yōu)椋何恢?擴(kuò)容前容量
                            else {
                                if (hiTail == null)
                                    hiHead = e;
                                else
                                    hiTail.next = e;
                                hiTail = e;
                            }
                        } while ((e = next) != null);
                        if (loTail != null) {
                            loTail.next = null;
                            newTab[j] = loHead;
                        }
                        if (hiTail != null) {
                            hiTail.next = null;
                            newTab[j + oldCap] = hiHead;
                        }
                    }
                }
            }
        }
        return newTab;
    }

擴(kuò)容時(shí)候怎么重新確定元素在數(shù)組中的位置,我們看到是由 if ((e.hash & oldCap) == 0) 確定的莺债。

hash HEX(97)  = 0110 0001
n    HEX(16)  = 0001 0000
--------------------------
         結(jié)果  = 0000 0000
# e.hash & oldCap = 0 計(jì)算得到位置還是擴(kuò)容前位置
    
hash HEX(17)  = 0001 0001
n    HEX(16)  = 0001 0000
--------------------------
         結(jié)果  = 0001 0000
#  e.hash & oldCap != 0 計(jì)算得到位置是擴(kuò)容前位置+擴(kuò)容前容量

通過(guò)上面的分析也可以看出齐邦,只有在每次容量都是2的次方的情況下才能使用 if ((e.hash & oldCap) == 0) 判斷擴(kuò)容后的位置。

7. HashMap 中的紅黑樹(shù)

HashMap 在 Java 8 中的實(shí)現(xiàn)增加了紅黑樹(shù)我纪,當(dāng)鏈表節(jié)點(diǎn)達(dá)到 8 個(gè)的時(shí)候浅悉,會(huì)把鏈表轉(zhuǎn)換成紅黑樹(shù)券犁,低于 6 個(gè)的時(shí)候族操,會(huì)退回鏈表。究其原因是因?yàn)楫?dāng)節(jié)點(diǎn)過(guò)多時(shí)泼舱,使用紅黑樹(shù)可以更高效的查找到節(jié)點(diǎn)娇昙。畢竟紅黑樹(shù)是一種二叉查找樹(shù)笤妙。

  1. 節(jié)點(diǎn)個(gè)數(shù)是多少的時(shí)候,鏈表會(huì)轉(zhuǎn)變成紅黑樹(shù)股毫。

    鏈表節(jié)點(diǎn)個(gè)數(shù)大于等于 8 時(shí)铃诬,鏈表會(huì)轉(zhuǎn)換成樹(shù)結(jié)構(gòu)。

  2. 節(jié)點(diǎn)個(gè)數(shù)是多少的時(shí)候兵志,紅黑樹(shù)會(huì)退回鏈表想罕。

    節(jié)點(diǎn)個(gè)數(shù)小于等于 6 時(shí)霉涨,樹(shù)會(huì)轉(zhuǎn)變成鏈表嵌纲。

  3. 為什么轉(zhuǎn)變條件 8 和 6 有一個(gè)差值腥沽。

    如果沒(méi)有差值,都是 8 师溅,那么如果頻繁的插入刪除元素墓臭,鏈表個(gè)數(shù)又剛好在 8 徘徊妖谴,那么就會(huì)頻繁的發(fā)生鏈表轉(zhuǎn)樹(shù)膝舅,樹(shù)轉(zhuǎn)鏈表。

8. 為啥容量都是2的冪洼滚?

容量是2的冪時(shí)遥巴,key 的 hash 值然后 & (容量-1) 確定位置時(shí)碰撞概率會(huì)比較低享幽,因?yàn)槿萘繛?2 的冪時(shí),減 1 之后的二進(jìn)制數(shù)為全1摆霉,這樣與運(yùn)算的結(jié)果就等于 hash值后面與 1 進(jìn)行與運(yùn)算的幾位斯入。

下面是個(gè)例子。

hash HEX(97)  = 0110 0001
n-1  HEX(15)  = 0000 1111
--------------------------
         結(jié)果  = 0000 0001
# 計(jì)算得到位置是 1
hash HEX(99)  = 0110 0011
n-1  HEX(15)  = 0000 1111
--------------------------
         結(jié)果  = 0000 0011
# 計(jì)算得到位置是 3
hash HEX(101)  = 0110 0101
n-1  HEX(15)   = 0000 1111
--------------------------
         結(jié)果   = 0000 0101
# 計(jì)算得到位置是 5

如果是其他的容量值增蹭,假設(shè)是9滋迈,進(jìn)行與運(yùn)算結(jié)果碰撞的概率就比較大饼灿。

hash HEX(97)  = 0110 0001
n-1  HEX(09)  = 0000 1001
--------------------------
         結(jié)果  = 0000 0001
# 計(jì)算得到位置是 1
hash HEX(99)  = 0110 0011
n-1  HEX(09)  = 0000 1001
--------------------------
         結(jié)果  = 0000 0001
# 計(jì)算得到位置是 1
hash HEX(101)  = 0110 0101
n-1  HEX(09)   = 0000 1001
--------------------------
         結(jié)果   = 0000 0001
# 計(jì)算得到位置是 1

另外碍彭,每次都是 2 的冪也可以讓 HashMap 擴(kuò)容時(shí)可以方便的重新計(jì)算位置悼潭。

hash HEX(97)  = 0110 0001
n-1  HEX(15)  = 0000 1111
--------------------------
         結(jié)果  = 0000 0001
# 計(jì)算得到位置是 1
    
hash HEX(97)  = 0110 0001
n-1  HEX(31)  = 0001 1111
--------------------------
         結(jié)果  = 0000 0001
# 計(jì)算得到位置是 1

9. 快速失斀⑼省(fail—fast)

HashMap 遍歷使用的是一種快速失敗機(jī)制,它是 Java 非安全集合中的一種普遍機(jī)制略就,這種機(jī)制可以讓集合在遍歷時(shí)表牢,如果有線程對(duì)集合進(jìn)行了修改掖疮、刪除、增加操作恼布,會(huì)觸發(fā)并發(fā)修改異常折汞。

它的實(shí)現(xiàn)機(jī)制是在遍歷前保存一份 modCount 盖腿,在每次獲取下一個(gè)要遍歷的元素時(shí)會(huì)對(duì)比當(dāng)前的 modCount 和保存的 modCount 是否相等。

快速失敗也可以看作是一種安全機(jī)制鸟款,這樣在多線程操作不安全的集合時(shí),由于快速失敗的機(jī)制组哩,會(huì)拋出異常伶贰。

10. 線程安全的 Map

  1. 使用 Collections.synchronizedMap(Map) 創(chuàng)建線程安全的 Map.

    實(shí)現(xiàn)原理:有一個(gè)變量 final Object mutex; 罐栈,操作方法都加了這個(gè) synchronized (mutex) 排它鎖荠诬。

  2. 使用 Hashtable

  3. 使用 ConcurrentHashMap

<完>
這篇文章到這里就結(jié)束了,如果你覺(jué)得文章不錯(cuò)可以關(guān)注我的公眾號(hào)望迎。 公眾號(hào)滿載干貨凌外,童叟無(wú)欺康辑。
也可以訪問(wèn)我的個(gè)人網(wǎng)站:https://www.wdbyte.com
[圖片上傳失敗...(image-84f936-1585702855127)]

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末疮薇,一起剝皮案震驚了整個(gè)濱河市我注,隨后出現(xiàn)的幾起案子但骨,更是在濱河造成了極大的恐慌,老刑警劉巖掠抬,帶你破解...
    沈念sama閱讀 217,657評(píng)論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件两波,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡单起,警方通過(guò)查閱死者的電腦和手機(jī)劣坊,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,889評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門讼稚,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人帮寻,你說(shuō)我怎么就攤上這事固逗∨褐模” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 164,057評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵贝攒,是天一觀的道長(zhǎng)隘弊。 經(jīng)常有香客問(wèn)我梨熙,道長(zhǎng)刀诬,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,509評(píng)論 1 293
  • 正文 為了忘掉前任质欲,我火速辦了婚禮把敞,結(jié)果婚禮上奋早,老公的妹妹穿的比我還像新娘。我一直安慰自己耽装,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,562評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布规个。 她就那樣靜靜地躺著诞仓,像睡著了一般墅拭。 火紅的嫁衣襯著肌膚如雪涣狗。 梳的紋絲不亂的頭發(fā)上澄港,一...
    開(kāi)封第一講書(shū)人閱讀 51,443評(píng)論 1 302
  • 那天内狗,我揣著相機(jī)與錄音唤蔗,去河邊找鬼措译。 笑死,一個(gè)胖子當(dāng)著我的面吹牛规哪,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播蝠嘉,決...
    沈念sama閱讀 40,251評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼蚤告,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼服爷!你這毒婦竟也來(lái)了获诈?” 一聲冷哼從身側(cè)響起舔涎,我...
    開(kāi)封第一講書(shū)人閱讀 39,129評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤亡嫌,失蹤者是張志新(化名)和其女友劉穎挟冠,沒(méi)想到半個(gè)月后袍睡,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,561評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡持舆,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,779評(píng)論 3 335
  • 正文 我和宋清朗相戀三年逸寓,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了竹伸。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片簇宽。...
    茶點(diǎn)故事閱讀 39,902評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡魏割,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出拜银,到底是詐尸還是另有隱情遭垛,我是刑警寧澤,帶...
    沈念sama閱讀 35,621評(píng)論 5 345
  • 正文 年R本政府宣布泵督,位于F島的核電站小腊,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏溢豆。R本人自食惡果不足惜漩仙,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,220評(píng)論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望卷仑。 院中可真熱鬧锡凝,春花似錦垢啼、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,838評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)惯疙。三九已至,卻和暖如春霉颠,著一層夾襖步出監(jiān)牢的瞬間掉分,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,971評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留不从,地道東北人犁跪。 一個(gè)月前我還...
    沈念sama閱讀 48,025評(píng)論 2 370
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像寝优,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子孟抗,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,843評(píng)論 2 354

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