數(shù)據(jù)結(jié)構(gòu)解析-HashMap

概要

HashMap在JDK1.8之前的實(shí)現(xiàn)方式 數(shù)組+鏈表,但是在JDK1.8后對(duì)HashMap進(jìn)行了底層優(yōu)化,改為了由 數(shù)組+鏈表+紅黑樹實(shí)現(xiàn),主要的目的是提高查找效率来累。

如圖所示:

JDK版本 實(shí)現(xiàn)方式 節(jié)點(diǎn)數(shù)>=8 節(jié)點(diǎn)數(shù)<=6
1.8以前 數(shù)組+單向鏈表 數(shù)組+單向鏈表 數(shù)組+單向鏈表
1.8以后 數(shù)組+單向鏈表+紅黑樹 數(shù)組+紅黑樹 數(shù)組+單向鏈表

HashMap

1.繼承關(guān)系

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

2.常量&構(gòu)造方法

//這兩個(gè)是限定值 當(dāng)節(jié)點(diǎn)數(shù)大于8時(shí)會(huì)轉(zhuǎn)為紅黑樹存儲(chǔ)
static final int TREEIFY_THRESHOLD = 8;

//當(dāng)節(jié)點(diǎn)數(shù)小于6時(shí)會(huì)轉(zhuǎn)為單向鏈表存儲(chǔ)
static final int UNTREEIFY_THRESHOLD = 6;

//紅黑樹最小長(zhǎng)度為 64
static final int MIN_TREEIFY_CAPACITY = 64;

//HashMap容量初始大小
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16

//HashMap容量極限
static final int MAXIMUM_CAPACITY = 1 << 30;

//負(fù)載因子默認(rèn)大小
static final float DEFAULT_LOAD_FACTOR = 0.75f;

//Node是Map.Entry接口的實(shí)現(xiàn)類
//在此存儲(chǔ)數(shù)據(jù)的Node數(shù)組容量是2次冪
//每一個(gè)Node本質(zhì)都是一個(gè)單向鏈表
transient Node<K,V>[] table;

//HashMap大小,它代表HashMap保存的鍵值對(duì)的多少
transient int size;

//HashMap被改變的次數(shù)
transient int modCount;

//下一次HashMap擴(kuò)容的大小
int threshold;

//存儲(chǔ)負(fù)載因子的常量
final float loadFactor;

    
//默認(rèn)的構(gòu)造函數(shù)
public HashMap() {
    this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
}

//指定容量大小
public HashMap(int initialCapacity) {
    this(initialCapacity, DEFAULT_LOAD_FACTOR);
}

//指定容量大小和負(fù)載因子大小
public HashMap(int initialCapacity, float loadFactor) {
    //指定的容量大小不可以小于0,否則將拋出IllegalArgumentException異常
    if (initialCapacity < 0)
        throw new IllegalArgumentException("Illegal initial capacity: " +
                initialCapacity);
     //判定指定的容量大小是否大于HashMap的容量極限
    if (initialCapacity > MAXIMUM_CAPACITY)
        initialCapacity = MAXIMUM_CAPACITY;
   //指定的負(fù)載因子不可以小于0或?yàn)镹ull乐导,若判定成立則拋出IllegalArgumentException異常
    if (loadFactor <= 0 || Float.isNaN(loadFactor))
        throw new IllegalArgumentException("Illegal load factor: " +
                loadFactor);
    this.loadFactor = loadFactor;
    // 設(shè)置“HashMap閾值”肩民,當(dāng)HashMap中存儲(chǔ)數(shù)據(jù)的數(shù)量達(dá)到threshold時(shí)袱蚓,就需要將HashMap的容量加倍。
    this.threshold = tableSizeFor(initialCapacity);
}

//傳入一個(gè)Map集合,將Map集合中元素Map.Entry全部添加進(jìn)HashMap實(shí)例中
public HashMap(Map<? extends K, ? extends V> m) {
    this.loadFactor = DEFAULT_LOAD_FACTOR;
    //此構(gòu)造方法主要實(shí)現(xiàn)了Map.putAll()
    putMapEntries(m, false);
}

3.Node單向鏈表的實(shí)現(xiàn)

//實(shí)現(xiàn)了Map.Entry接口

static class Node<K,V> implements Map.Entry<K,V> {
    final int hash;
    final K key;
    V value;
    Node<K,V> next;
 
     //構(gòu)造函數(shù)
    Node(int hash, K key, V value, Node<K,V> next) {
        this.hash = hash;
        this.key = key;
        this.value = value;
        this.next = next;
    }
    
    public final K getKey()        { return key; }
    public final V getValue()      { return value; }
    public final String toString() { return key + "=" + value; }
    
    public final int hashCode() {
        return Objects.hashCode(key) ^ Objects.hashCode(value);
    }
    
    public final V setValue(V newValue) {
        V oldValue = value;
        value = newValue;
        return oldValue;
    }

     //equals屬性對(duì)比
    public final boolean equals(Object o) {
        if (o == this)
            return true;
        if (o instanceof Map.Entry) {
            Map.Entry<?,?> e = (Map.Entry<?,?>)o;
            if (Objects.equals(key, e.getKey()) &&
                    Objects.equals(value, e.getValue()))
                return true;
        }
        return false;
    }
}

4.TreeNode紅黑樹實(shí)現(xiàn)


static final class TreeNode<K,V> extends LinkedHashMap.LinkedHashMapEntry<K,V> {
    TreeNode<K,V> parent;  // 紅黑樹的根節(jié)點(diǎn)
    TreeNode<K,V> left; //左樹
    TreeNode<K,V> right; //右樹
    TreeNode<K,V> prev;    // 上一個(gè)幾點(diǎn)
    boolean red; //是否是紅樹
    
    TreeNode(int hash, K key, V val, Node<K,V> next) {
       super(hash, key, val, next);
    }

    /**
      * 根節(jié)點(diǎn)的實(shí)現(xiàn)
      **/
    final TreeNode<K,V> root() {
       for (TreeNode<K,V> r = this, p;;) {
         if ((p = r.parent) == null)
         return r;
         r = p;
      }
  }
...

5.Hash的計(jì)算實(shí)現(xiàn)

//主要是將傳入的參數(shù)key本身的hashCode與h無符號(hào)右移16位進(jìn)行二進(jìn)制異或運(yùn)算得出一個(gè)新的hash值
static final int hash(Object key) {
   int h;
   return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}

延伸講解

5.1.下面的做了一個(gè)例子講解,經(jīng)過hash函數(shù)計(jì)算后得到的key的hash值

image

5.2那為什么要這么做呢犯建?直接通過key.hashCode()獲取hash不得了嗎?為什么在右移16位后進(jìn)行異或運(yùn)算诸狭?

答案 : 與HashMap的table數(shù)組下計(jì)算標(biāo)有關(guān)系

我們?cè)谙旅嬷v解的put/get函數(shù)代碼塊中都出現(xiàn)了這樣一段代碼

//put函數(shù)代碼塊中
tab[i = (n - 1) & hash])
//get函數(shù)代碼塊中
tab[(n - 1) & hash])

我們知道這段代碼是根據(jù)索引得到tab中節(jié)點(diǎn)數(shù)據(jù),它是如何與hash進(jìn)行與運(yùn)算后得到索引位置呢! 假設(shè)tab.length()=1<<4

image

這樣做的根本原因是當(dāng)發(fā)生較大碰撞時(shí)也用樹形存儲(chǔ)降低了沖突。既減少了系統(tǒng)的開銷

6.HashMap.put的源碼實(shí)現(xiàn)


public V put(K key, V value) {
    return putVal(hash(key), key, value, false, true);
}
//HashMap.put的具體實(shí)現(xiàn)
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
               boolean evict){
    Node<K,V>[] tab; Node<K,V> p; int n, i;
    //判定table不為空并且table長(zhǎng)度不可為0,否則將從resize函數(shù)中獲取
    if ((tab = table) == null || (n = tab.length) == 0)
        n = (tab = resize()).length;
    //這樣寫法有點(diǎn)繞,其實(shí)這里就是通過索引獲取table數(shù)組中的一個(gè)元素看是否為Nul
    if ((p = tab[i = (n - 1) & hash]) == null)
        //若判斷成立,則New一個(gè)Node出來賦給table中指定索引下的這個(gè)元素
        tab[i] = newNode(hash, key, value, null);
    else {  //若判斷不成立
        Node<K,V> e; K k;
        //對(duì)這個(gè)元素進(jìn)行Hash和key值匹配
        if (p.hash == hash &&
                ((k = p.key) == key || (key != null && key.equals(k))))
            e = p;
        else if (p instanceof TreeNode) //如果數(shù)組中的這個(gè)元素P是TreeNode類型
            //判定成功則在紅黑樹中查找符合的條件的節(jié)點(diǎn)并返回此節(jié)點(diǎn)
            e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
        else { //若以上條件均判斷失敗辟躏,則執(zhí)行以下代碼
            //向Node單向鏈表中添加數(shù)據(jù)
            for (int binCount = 0; ; ++binCount) {
                if ((e = p.next) == null) {
                    p.next = newNode(hash, key, value, null);
                    //若節(jié)點(diǎn)數(shù)大于等于8
                    if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                        //轉(zhuǎn)換為紅黑樹
                        treeifyBin(tab, hash);
                    break;
                }
                if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                    break;
                p = e; //p記錄下一個(gè)節(jié)點(diǎn)
            }
        }
        if (e != null) { // existing mapping for key
            V oldValue = e.value;
            if (!onlyIfAbsent || oldValue == null)
                e.value = value;
            afterNodeAccess(e);
            return oldValue;
        }
    }
    ++modCount;
    if (++size > threshold) //判斷是否需要擴(kuò)容
        resize();
    afterNodeInsertion(evict);
    return null;
}

梳理以下HashMap.put函數(shù)的執(zhí)行過程

1.首先獲取Node數(shù)組table對(duì)象和長(zhǎng)度谷扣,若table為null或長(zhǎng)度為0,則調(diào)用resize()擴(kuò)容方法獲取table最新對(duì)象,并通過此對(duì)象獲取長(zhǎng)度大小2.判定數(shù)組中指定索引下的節(jié)點(diǎn)是否為Null会涎,若為Null 則new出一個(gè)單向鏈表賦給table中索引下的這個(gè)節(jié)點(diǎn)3.若判定不為Null,我們的判斷再做分支3.1 首先對(duì)hash和key進(jìn)行匹配,若判定成功直接賦予e3.2 若匹配判定失敗,則進(jìn)行類型匹配是否為TreeNode 若判定成功則在紅黑樹中查找符合條件的節(jié)點(diǎn)并將其回傳賦給e3.3 若以上判定全部失敗則進(jìn)行最后操作,向單向鏈表中添加數(shù)據(jù)若單向鏈表的長(zhǎng)度大于等于8,則將其轉(zhuǎn)為紅黑樹保存裹匙,記錄下一個(gè)節(jié)點(diǎn),對(duì)e進(jìn)行判定若成功則返回舊值4.最后判定數(shù)組大小需不需要擴(kuò)容

7.HashMap.get的源碼實(shí)現(xiàn)

//這里直接調(diào)用getNode函數(shù)實(shí)現(xiàn)方法
public V get(Object key) {
    Node<K,V> e;
    //經(jīng)過hash函數(shù)運(yùn)算 獲取key的hash值
    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;
    //判定三個(gè)條件 table不為Null & table的長(zhǎng)度大于0 & table指定的索引值不為Null
    if ((tab = table) != null && (n = tab.length) > 0 &&
            (first = tab[(n - 1) & hash]) != null) {
        //判定 匹配hash值 & 匹配key值 成功則返回 該值
        if (first.hash == hash &&
                ((k = first.key) == key || (key != null && key.equals(k))))
            return first;
        //若 first節(jié)點(diǎn)的下一個(gè)節(jié)點(diǎn)不為Null
        if ((e = first.next) != null) {
            if (first instanceof TreeNode) //若first的類型為TreeNode 紅黑樹
                //通過紅黑樹查找匹配值 并返回
                return ((TreeNode<K,V>)first).getTreeNode(hash, key);
            //若上面判定不成功 則認(rèn)為下一個(gè)節(jié)點(diǎn)為單向鏈表,通過循環(huán)匹配值
            do {
                if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                    //匹配成功后返回該值
                    return e;
            } while ((e = e.next) != null);
        }
    }
    return null;
}

梳理以下HashMap.get函數(shù)的執(zhí)行過程

1.判定三個(gè)條件 table不為Null & table的長(zhǎng)度大于0 & table指定的索引值不為Null

2.判定 匹配hash值 & 匹配key值 成功則返回 該值

3.若 first節(jié)點(diǎn)的下一個(gè)節(jié)點(diǎn)不為Null

3.1 若first的類型為TreeNode 紅黑樹 通過紅黑樹查找匹配值 并返回查詢值

3.2若上面判定不成功 則認(rèn)為下一個(gè)節(jié)點(diǎn)為單向鏈表,通過循環(huán)匹配值

8.HashMap擴(kuò)容原理分析

//重新設(shè)置table大小/擴(kuò)容 并返回?cái)U(kuò)容的Node數(shù)組即HashMap的最新數(shù)據(jù)

final Node<K,V>[] resize() {
    Node<K,V>[] oldTab = table; //table賦予oldTab作為擴(kuò)充前的table數(shù)據(jù)

    int oldCap = (oldTab == null) ? 0 : oldTab.length;
    int oldThr = threshold;
    int newCap, newThr = 0;
    if (oldCap > 0) {
        //判定數(shù)組是否已達(dá)到極限大小,若判定成功將不再擴(kuò)容末秃,直接將老表返回
        if (oldCap >= MAXIMUM_CAPACITY) {
            threshold = Integer.MAX_VALUE;
            return oldTab;
        }
        //若新表大小(oldCap*2)小于數(shù)組極限大小 并且 老表大于等于數(shù)組初始化大小

        else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
                oldCap >= DEFAULT_INITIAL_CAPACITY)
            //舊數(shù)組大小oldThr 經(jīng)二進(jìn)制運(yùn)算向左位移1個(gè)位置 即 oldThr*2當(dāng)作新數(shù)組的大小
            newThr = oldThr << 1; // double threshold
    }
    //若老表中下次擴(kuò)容大小oldThr大于0
    else if (oldThr > 0)
        newCap = oldThr;  //將oldThr賦予控制新表大小的newCap
    else { //若其他情況則將獲取初始默認(rèn)大小
        newCap = DEFAULT_INITIAL_CAPACITY;
        newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
    }
    //若新表的下表下一次擴(kuò)容大小為0
    if (newThr == 0) {
        float ft = (float)newCap * loadFactor;  //通過新表大小*負(fù)載因子獲取
        newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
                (int)ft : Integer.MAX_VALUE);
    }
    threshold = newThr; //下次擴(kuò)容的大小

    @SuppressWarnings({"rawtypes","unchecked"})
    Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
    table = newTab; //將當(dāng)前表賦予table

    if (oldTab != null) { //若oldTab中有值需要通過循環(huán)將oldTab中的值保存到新表中
        for (int j = 0; j < oldCap; ++j) {
            Node<K,V> e;
            if ((e = oldTab[j]) != null) {//獲取老表中第j個(gè)元素 賦予e
                oldTab[j] = null; //并將老表中的元素?cái)?shù)據(jù)置Null
                if (e.next == null) //若此判定成立 則代表e的下面沒有節(jié)點(diǎn)了
                    newTab[e.hash & (newCap - 1)] = e; //將e直接存于新表的指定位置
                else if (e instanceof TreeNode)  //若e是TreeNode類型
                    //分割樹概页,將新表和舊表分割成兩個(gè)樹,并判斷索引處節(jié)點(diǎn)的長(zhǎng)度是否需要轉(zhuǎn)換成紅黑樹放入新表存儲(chǔ)
                    ((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
                else { // preserve order
                    Node<K,V> loHead = null, loTail = null; //存儲(chǔ)與舊索引的相同的節(jié)點(diǎn)
                    Node<K,V> hiHead = null, hiTail = null; //存儲(chǔ)與新索引相同的節(jié)點(diǎn)
                    Node<K,V> next;
                    //通過Do循環(huán) 獲取新舊索引的節(jié)點(diǎn)
                    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);
                    //通過判定將舊數(shù)據(jù)和新數(shù)據(jù)存儲(chǔ)到新表指定的位置

                    if (loTail != null) {
                        loTail.next = null;
                        newTab[j] = loHead;
                    }
                    if (hiTail != null) {
                        hiTail.next = null;
                        newTab[j + oldCap] = hiHead;
                    }
                }
            }
        }
    }
    //返回新表
    return newTab;
}

梳理以下HashMap.resize函數(shù)的執(zhí)行過程

如圖所示:

1.判定數(shù)組是否已達(dá)到極限大小练慕,若判定成功將不再擴(kuò)容惰匙,直接將老表返回2.若新表大小(oldCap2)小于數(shù)組極限大小&老表大于等于數(shù)組初始化大小 判定成功則 舊數(shù)組大小oldThr 經(jīng)二進(jìn)制運(yùn)算向左位移1個(gè)位置 即 oldThr2當(dāng)作新數(shù)組的大小2.1. 若[2]的判定不成功,則繼續(xù)判定 oldThr (代表 老表的下一次擴(kuò)容量)大于0铃将,若判定成功 則將oldThr賦給newCap作為新表的容量2.2 若 [2] 和[2.1]判定都失敗,則走默認(rèn)賦值 代表 表為初次創(chuàng)建3.確定下一次表的擴(kuò)容量, 將新表賦予當(dāng)前表4.通過for循環(huán)將老表中的值存入擴(kuò)容后的新表中4.1 獲取舊表中指定索引下的Node對(duì)象 賦予e 并將舊表中的索引位置數(shù)據(jù)置空4.2 若e的下面沒有其他節(jié)點(diǎn)則將e直接賦到新表中的索引位置4.3 若e的類型為TreeNode紅黑樹類型4.3.1 分割樹项鬼,將新表和舊表分割成兩個(gè)樹,并判斷索引處節(jié)點(diǎn)的長(zhǎng)度是否需要轉(zhuǎn)換成紅黑樹放入新表存儲(chǔ)4.3.2 通過Do循環(huán) 不斷獲取新舊索引的節(jié)點(diǎn)5.最后返回值為 擴(kuò)容后的新表劲阎。

9.HashMap 的treeifyBin講解

final void treeifyBin(Node<K,V>[] tab, int hash) {
    int n, index; Node<K,V> e;
    //做判定 tab 為Null 或 tab的長(zhǎng)度小于 紅黑樹最小容量
    if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)
        //則通過擴(kuò)容,擴(kuò)容table數(shù)組大小
        resize();
        //做判定 若tab索引位置下數(shù)據(jù)不為空
    else if ((e = tab[index = (n - 1) & hash]) != null) {
        //定義兩個(gè)紅黑樹绘盟;分別表示頭部節(jié)點(diǎn)、尾部節(jié)點(diǎn)
        TreeNode<K,V> hd = null, tl = null;
        //通過循環(huán)將單向鏈表轉(zhuǎn)換為紅黑樹存儲(chǔ)
        do {
            //將單向鏈表轉(zhuǎn)換為紅黑樹
            TreeNode<K,V> p = replacementTreeNode(e, null);
            if (tl == null) //若頭部節(jié)點(diǎn)為Null,則說明該樹沒有根節(jié)點(diǎn)
                hd = p;
            else {
                p.prev = tl; //指向父節(jié)點(diǎn)
                tl.next = p; //指向下一個(gè)節(jié)點(diǎn)
            }
            tl = p; //將當(dāng)前節(jié)點(diǎn)設(shè)尾節(jié)點(diǎn)

        } while ((e = e.next) != null); //若下一個(gè)不為Null,則繼續(xù)遍歷
        //紅黑樹轉(zhuǎn)換后,替代原位置上的單項(xiàng)鏈表
        if ((tab[index] = hd) != null)
            hd.treeify(tab); //  構(gòu)建紅黑樹悯仙,以頭部節(jié)點(diǎn)定為根節(jié)點(diǎn)
    }
}

TreeNode<K,V> replacementTreeNode(Node<K,V> p, Node<K,V> next) {
    return new TreeNode<>(p.hash, p.key, p.value, next);
}

梳理以下HashMap.treeifyBin函數(shù)的執(zhí)行過程

1.做判定 tab 為Null 或 tab的長(zhǎng)度小于紅黑樹最小容量奥此, 判定成功則通過擴(kuò)容,擴(kuò)容table數(shù)組大小2.做判定 若tab索引位置下數(shù)據(jù)不為空,判定成功則通過循環(huán)將單向鏈表轉(zhuǎn)換為紅黑樹存儲(chǔ)2.1 通過Do循環(huán)將當(dāng)前節(jié)點(diǎn)下的單向鏈表轉(zhuǎn)換為紅黑樹雁比,若下一個(gè)不為Null,則繼續(xù)遍歷2.2 構(gòu)建紅黑樹稚虎,以頭部節(jié)點(diǎn)定為根節(jié)點(diǎn)

轉(zhuǎn)載地址:https://mp.weixin.qq.com/s/LxASRnHHseS7o2teydqtFw

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市偎捎,隨后出現(xiàn)的幾起案子蠢终,更是在濱河造成了極大的恐慌,老刑警劉巖茴她,帶你破解...
    沈念sama閱讀 212,080評(píng)論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件寻拂,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡丈牢,警方通過查閱死者的電腦和手機(jī)祭钉,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,422評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來己沛,“玉大人慌核,你說我怎么就攤上這事∩昴幔” “怎么了垮卓?”我有些...
    開封第一講書人閱讀 157,630評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)师幕。 經(jīng)常有香客問我粟按,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,554評(píng)論 1 284
  • 正文 為了忘掉前任灭将,我火速辦了婚禮疼鸟,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘庙曙。我一直安慰自己空镜,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,662評(píng)論 6 386
  • 文/花漫 我一把揭開白布矾利。 她就那樣靜靜地躺著姑裂,像睡著了一般。 火紅的嫁衣襯著肌膚如雪男旗。 梳的紋絲不亂的頭發(fā)上舶斧,一...
    開封第一講書人閱讀 49,856評(píng)論 1 290
  • 那天,我揣著相機(jī)與錄音察皇,去河邊找鬼茴厉。 笑死,一個(gè)胖子當(dāng)著我的面吹牛什荣,可吹牛的內(nèi)容都是我干的矾缓。 我是一名探鬼主播,決...
    沈念sama閱讀 39,014評(píng)論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼稻爬,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼嗜闻!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起桅锄,我...
    開封第一講書人閱讀 37,752評(píng)論 0 268
  • 序言:老撾萬榮一對(duì)情侶失蹤琉雳,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后友瘤,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體翠肘,經(jīng)...
    沈念sama閱讀 44,212評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,541評(píng)論 2 327
  • 正文 我和宋清朗相戀三年辫秧,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了束倍。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,687評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡盟戏,死狀恐怖绪妹,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情抓半,我是刑警寧澤喂急,帶...
    沈念sama閱讀 34,347評(píng)論 4 331
  • 正文 年R本政府宣布,位于F島的核電站笛求,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜探入,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,973評(píng)論 3 315
  • 文/蒙蒙 一狡孔、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧蜂嗽,春花似錦苗膝、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,777評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至病附,卻和暖如春问窃,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背完沪。 一陣腳步聲響...
    開封第一講書人閱讀 32,006評(píng)論 1 266
  • 我被黑心中介騙來泰國(guó)打工域庇, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人覆积。 一個(gè)月前我還...
    沈念sama閱讀 46,406評(píng)論 2 360
  • 正文 我出身青樓听皿,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親宽档。 傳聞我的和親對(duì)象是個(gè)殘疾皇子尉姨,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,576評(píng)論 2 349