SimpleArrayMap 源碼分析

SimpleArrayMap

SimpleArrayMap 是 Andorid V4 包提供的一種用來代替 HashMap 的數(shù)據(jù)結(jié)構(gòu)讼庇,由于 HashMap 在數(shù)據(jù)容量過大時(shí)時(shí)間復(fù)雜度會(huì)越來與趨近于 O(N) , 故而效率不高昵仅。SimpleArrayMap 的實(shí)現(xiàn)方式和工作過程使其內(nèi)存占用更小,在數(shù)據(jù)量不大時(shí)效率更高须眷,所以在 Android 開發(fā)中我們可以擇機(jī)選擇適合的方式來實(shí)現(xiàn) Map

下面我們就來一步步分析,為什么 SimpleArrayMap 可以實(shí)現(xiàn)更小的內(nèi)存占用和更加高效查找

一、成員變量

/**
 * ArrayMap 擴(kuò)容的最小數(shù)量
 */
private static final int BASE_SIZE = 4;

/**
 * 緩存數(shù)組的最大數(shù)量,使用小的緩存數(shù)組可以避免垃圾回收
 */
private static final int CACHE_SIZE = 10;

// 緩存所用的數(shù)組韭畸,以及已經(jīng)換的數(shù)量
static Object[] mBaseCache;
static int mBaseCacheSize;
static Object[] mTwiceBaseCache;
static int mTwiceBaseCacheSize;

/**
 * 存放 key 的 hash 值的數(shù)組
 */
int[] mHashes;
/**
 * 存放 key 和 value 的數(shù)組
 */
Object[] mArray;
/**
 * 已存鍵值對的數(shù)量
 */
int mSize;

二宇智、構(gòu)造方法

/**
 * 創(chuàng)建容量為空的 mHashes 和 mArray 數(shù)組
 */
public SimpleArrayMap() {
    mHashes = ContainerHelpers.EMPTY_INTS;
    mArray = ContainerHelpers.EMPTY_OBJECTS;
    mSize = 0;
}

/**
 * 根據(jù)給定的初始容量創(chuàng)建 mHashes 和 mArray 數(shù)組
 */
public SimpleArrayMap(int capacity) {
    if (capacity == 0) {
        mHashes = ContainerHelpers.EMPTY_INTS;
        mArray = ContainerHelpers.EMPTY_OBJECTS;
    } else {
        allocArrays(capacity);
    }
    mSize = 0;
}

/**
 * 根據(jù)給定的 ArrayMap 初始化
 */
public SimpleArrayMap(SimpleArrayMap map) {
    this(); // 調(diào)用無參數(shù)構(gòu)造函數(shù)
    if (map != null) {
        // 將給定的 ArrayMap 添加
        putAll(map); 
    }
}

/**
 * 將給定的 ArrayMap 添加本身
 */
public void putAll(SimpleArrayMap<? extends K, ? extends V> array) {
    final int N = array.mSize;
    
    // 確認(rèn)容量支持添加給定的 ArrayMap
    ensureCapacity(mSize + N);

    if (mSize == 0) { // 如果原數(shù)組容量為 0蔓搞,則將給定數(shù)組添加進(jìn)新創(chuàng)建的數(shù)組中
        if (N > 0) {
            System.arraycopy(array.mHashes, 0, mHashes, 0, N);
            System.arraycopy(array.mArray, 0, mArray, 0, N<<1);
            mSize = N;
        }
    } else { // 如果原數(shù)組容量不為 0,則遍歷給定數(shù)組随橘,將給定數(shù)組的值添加到原數(shù)組中
        for (int i=0; i<N; i++) {
            put(array.keyAt(i), array.valueAt(i));
        }
    }
}

/**
 * 確認(rèn)容量支持指定的最小數(shù)量喂分,如果當(dāng)前容量小于指定的數(shù)量,則以給定數(shù)量創(chuàng)建新的數(shù)組
 */
public void ensureCapacity(int minimumCapacity) {
    if (mHashes.length < minimumCapacity) {
        final int[] ohashes = mHashes;
        final Object[] oarray = mArray;
        
        // 以給定容量創(chuàng)建新的數(shù)組
        allocArrays(minimumCapacity); 
        if (mSize > 0) {
            System.arraycopy(ohashes, 0, mHashes, 0, mSize);
            System.arraycopy(oarray, 0, mArray, 0, mSize<<1);
        }
        
        // 緩存舊的 hash 數(shù)組
        freeArrays(ohashes, oarray, mSize); 
    }
}

SimpleArrayMap 有三個(gè)重載的構(gòu)造方法机蔗,其中給定容量和給定 ArrayMap 的構(gòu)造方法中涉及到的方法我們下面回一一解析

三蒲祈、插入方法 put()

public V put(K key, V value) {
    final int hash;
    int index;
    if (key == null) {
        hash = 0;
        
        // 查找對應(yīng) key 在 hash 值數(shù)組中的位置
        index = indexOfNull();  
    } else {
        hash = key.hashCode();
        
        // 查找對應(yīng) key 在 hash 值數(shù)組中的位置
        index = indexOf(key, hash); 
    }
    
    if (index >= 0) { // 存在甘萧,直接替換 array 數(shù)組中對應(yīng)位置的 value 值,并將舊值返回
        index = (index<<1) + 1;
        final V old = (V)mArray[index];
        mArray[index] = value;
        return old;
    }

    index = ~index; // 未找到情況梆掸,需要執(zhí)行插入操作
    if (mSize >= mHashes.length) { // 為新插入數(shù)組需要擴(kuò)容舊數(shù)組
        final int n = mSize >= (BASE_SIZE*2) ? (mSize+(mSize>>1)) // 當(dāng)前鍵值對數(shù)量大于等于最小擴(kuò)容數(shù)量的 2 倍時(shí)扬卷,n 等于 mSeze 的 1.5 倍
                : (mSize >= BASE_SIZE ? (BASE_SIZE*2) : BASE_SIZE); // 當(dāng)前鍵值對數(shù)量大于等于最小擴(kuò)容數(shù)量時(shí),n 等于最小擴(kuò)容量的 2 倍酸钦,否則就等于最小擴(kuò)容量

        // 保存舊的 hash 數(shù)組
        final int[] ohashes = mHashes; 
        final Object[] oarray = mArray;
        
        // 以最新的容量創(chuàng)建 hash 和 mArray 數(shù)組
        allocArrays(n); 

        if (mHashes.length > 0) { // 大于 0 的情況是創(chuàng)建新數(shù)組時(shí)使用了緩存
            // 將舊數(shù)組中的數(shù)據(jù)復(fù)制到新數(shù)組中
            System.arraycopy(ohashes, 0, mHashes, 0, ohashes.length); 
            System.arraycopy(oarray, 0, mArray, 0, oarray.length);
        }

        // 將舊數(shù)組添加到緩存
        freeArrays(ohashes, oarray, mSize);
    }

    if (index < mSize) { // 需要插入到舊數(shù)組中間
        // 將需要插入位置之后的數(shù)據(jù)右移一位
        System.arraycopy(mHashes, index, mHashes, index + 1, mSize - index);
        System.arraycopy(mArray, index << 1, mArray, (index + 1) << 1, (mSize - index) << 1);
    }

    // 在 hash 和 array 數(shù)組的對應(yīng)位置插入新值
    mHashes[index] = hash;
    mArray[index<<1] = key;
    mArray[(index<<1)+1] = value;
    
    mSize++; // 鍵值對數(shù)量加 1
    return null;
}

/**
 * 計(jì)算 key 為 null 的時(shí)候應(yīng)該插入的位置
 */
int indexOfNull() {
    final int N = mSize;

    // 鍵值對為 0 怪得,直接返回 0
    if (N == 0) {
        return ~0;
    }
    
    // 二分法找到對應(yīng) 0 值在 mHashes 數(shù)組中的位置
    int index = ContainerHelpers.binarySearch(mHashes, N, 0);

    // index 小于 0 說明 mHashes 數(shù)組中不存存在 0,則直接返回
    if (index < 0) {
        return index;
    }

    // mHashes 中有對應(yīng)值時(shí)卑硫,mArray 數(shù)組中對應(yīng)位置為 null徒恋,新值可以直接插入,此時(shí)直接返回 0 在 mHashes 中的位置
    if (null == mArray[index<<1]) {
        return index;
    }

    // mArray 數(shù)組中對應(yīng)位置不為 null欢伏,此時(shí)向后尋找 hash 為 0 但 mArray 對應(yīng)位置為 null 的索引
    int end;
    for (end = index + 1; end < N && mHashes[end] == 0; end++) {
        if (null == mArray[end << 1]) return end;
    }

    // mArray 數(shù)組中對應(yīng)位置不為 null入挣,此時(shí)向前尋找 hash 為 0 但 mArray 對應(yīng)位置為 null 的索引
    for (int i = index - 1; i >= 0 && mHashes[i] == 0; i--) {
        if (null == mArray[i << 1]) return i;
    }

    // 如果未找到 mHashes 中值為 0 且 mArray 數(shù)組中對應(yīng)位置為 null 的索引,則返回當(dāng)前 hash 數(shù)組的長度值加 1 的值取反作為新插入的位置硝拧,表明需要插入新的位置
    return ~end;
}


/**
 * 計(jì)算 key 應(yīng)該插入的位置
 */
int indexOf(Object key, int hash) {
    final int N = mSize;

    // Important fast case: if nothing is in here, nothing to look for.
    if (N == 0) {
        return ~0;
    }

    int index = ContainerHelpers.binarySearch(mHashes, N, hash);

    // If the hash code wasn't found, then we have no entry for this key.
    if (index < 0) {
        return index;
    }

    // If the key at the returned index matches, that's what we want.
    if (key.equals(mArray[index<<1])) {
        return index;
    }

    // 這里與 indexOfNull() 方法中類似径筏,只不過將 mArray 數(shù)組中對應(yīng)位置的是否為 null 改為了判斷 mHashes 數(shù)組中的 key 是否相同,如果匹配成功則返回對應(yīng)位置
    int end;
    for (end = index + 1; end < N && mHashes[end] == hash; end++) {
        if (key.equals(mArray[end << 1])) return end;
    }

    // Search for a matching key before the index.
    for (int i = index - 1; i >= 0 && mHashes[i] == hash; i--) {
        if (key.equals(mArray[i << 1])) return i;
    }

    // 未匹配成功則返回需要插入的位置取反后的值障陶,表明需要插入新的位置
    return ~end;
}


/**
 * 緩存數(shù)據(jù) 由代碼分析可以看到最終添加到緩存的只是 mHashes 數(shù)組
 * hashes 的長度為最小擴(kuò)容量或者為最小擴(kuò)容量的兩倍匠璧,并且緩存的數(shù)量小于最大緩存容量時(shí),才執(zhí)行緩存操作
 */
private static void freeArrays(final int[] hashes, final Object[] array, final int size) {
    if (hashes.length == (BASE_SIZE*2)) {
        synchronized (ArrayMap.class) {
            if (mTwiceBaseCacheSize < CACHE_SIZE) {
                array[0] = mTwiceBaseCache; // 將舊的緩存數(shù)據(jù)放入 array[0] 的位置
                array[1] = hashes; // 將新的需要緩存的 hash 數(shù)組放入 array[1] 的位置
                
                // 通過遍歷 array 數(shù)組咸这,將其他位置置空
                for (int i=(size<<1)-1; i>=2; i--) {
                    array[i] = null;
                }

                // 將緩存索引重新指向最新的緩存數(shù)組
                mTwiceBaseCache = array;
                
                // 緩存數(shù)量加 1
                mTwiceBaseCacheSize++;
            }
        }
    } else if (hashes.length == BASE_SIZE) { // 同上
        synchronized (ArrayMap.class) {
            if (mBaseCacheSize < CACHE_SIZE) {
                array[0] = mBaseCache;
                array[1] = hashes;
                for (int i=(size<<1)-1; i>=2; i--) {
                    array[i] = null;
                }
                mBaseCache = array;
                mBaseCacheSize++;·
            }
        }
    }
}


/**
 * 有緩存時(shí)使用緩存創(chuàng)建 hash 數(shù)組
 */
private void allocArrays(final int size) {
    if (size == (BASE_SIZE*2)) {
        synchronized (ArrayMap.class) {
            if (mTwiceBaseCache != null) {
                final Object[] array = mTwiceBaseCache;
                mArray = array;
                mTwiceBaseCache = (Object[])array[0];
                mHashes = (int[])array[1]; // 使用緩存的 hash 數(shù)組
                array[0] = array[1] = null; // 緩存數(shù)量減 1
                mTwiceBaseCacheSize--;
                if (DEBUG) Log.d(TAG, "Retrieving 2x cache " + mHashes
                        + " now have " + mTwiceBaseCacheSize + " entries");
                return;
            }
        }
    } else if (size == BASE_SIZE) {
        synchronized (ArrayMap.class) {
            if (mBaseCache != null) {
                final Object[] array = mBaseCache;
                mArray = array;
                mBaseCache = (Object[])array[0];
                mHashes = (int[])array[1];
                array[0] = array[1] = null;
                mBaseCacheSize--;
                if (DEBUG) Log.d(TAG, "Retrieving 1x cache " + mHashes
                        + " now have " + mBaseCacheSize + " entries");
                return;
            }
        }
    }

    // 沒有緩存或者與緩存長度不匹配時(shí)夷恍,則直接創(chuàng)建對應(yīng)長度的 mHashes 數(shù)組和 mArray 數(shù)組
    mHashes = new int[size];
    mArray = new Object[size<<1];
}

從構(gòu)造函數(shù)和 put 方法來看,SimpleArrayMap 的實(shí)現(xiàn)方式為媳维,由一個(gè)存儲(chǔ) int 類型 key 的 hash 值的數(shù)組 mHashes 和一個(gè)同時(shí)存儲(chǔ) key 和 value 的 Object 類型的 mArray 數(shù)組構(gòu)成酿雪,mArray 會(huì)依次按照 key-value 的順序存儲(chǔ)數(shù)據(jù)

四、查找方法 get()

/**
 * 根據(jù) key 獲取 value 值
 */
public V get(Object key) {
    //  找到 key 的 hash 值在 mHashes 數(shù)組中的索引
    final int index = indexOfKey(key);
    
    // 返回 mArray 數(shù)組中對應(yīng)的 value 值
    return index >= 0 ? (V)mArray[(index<<1)+1] : null;
}

/**
 * 找到 key 的 hash 值在 mHashes 數(shù)組中的索引
 */
public int indexOfKey(Object key) {
    // indexOfNull 時(shí)當(dāng) key 為 null 時(shí)查找侄刽,indexOf 為 key 不為 null 時(shí)的查找指黎,上面分析 put() 方法時(shí)已經(jīng)解析過了
    return key == null ? indexOfNull() : indexOf(key, key.hashCode());
}

/**
 * 根據(jù) value 獲取 value 對應(yīng) key 在 mHashes 數(shù)組中的索引,區(qū)分 null 可非 null 情況
 */
int indexOfValue(Object value) {
    final int N = mSize*2;
    final Object[] array = mArray;
    if (value == null) {
        for (int i=1; i<N; i+=2) {
            if (array[i] == null) {
                return i>>1;
            }
        }
    } else {
        for (int i=1; i<N; i+=2) {
            if (value.equals(array[i])) {
                return i>>1;
            }
        }
    }
    return -1;
}

/**
 * 判斷 Map 中是否有指定 key
 */
public boolean containsKey(Object key) {
    return indexOfKey(key) >= 0;
}

五州丹、移除方法

/**
 * 移除指定 key 的鍵值對
 */
public V remove(Object key) {
    final int index = indexOfKey(key);
    if (index >= 0) {
        return removeAt(index);
    }

    return null;
}

/**
 * 移除指定 hash 數(shù)組中位置對應(yīng)的鍵值對
 */
public V removeAt(int index) {
    final Object old = mArray[(index << 1) + 1];
    if (mSize <= 1) { // 如果鍵值對數(shù)量為小于等于 1醋安,添加緩存后直接清空數(shù)組
        // Now empty.
        freeArrays(mHashes, mArray, mSize);
        mHashes = ContainerHelpers.EMPTY_INTS;
        mArray = ContainerHelpers.EMPTY_OBJECTS;
        mSize = 0;
    } else {
        // 如果數(shù)組長度過大,并且數(shù)組重的鍵值對太小墓毒,則需要縮小足夠的容量以降低數(shù)組的大小吓揪,但是不會(huì)縮小到 BASE_SIZE*2 的值,避免數(shù)組容量的搖擺
        if (mHashes.length > (BASE_SIZE*2) && mSize < mHashes.length/3) { 
            
            // 計(jì)算壓縮后的容量大小
            final int n = mSize > (BASE_SIZE*2) ? (mSize + (mSize>>1)) : (BASE_SIZE*2);

            final int[] ohashes = mHashes;
            final Object[] oarray = mArray;
            
            // 根據(jù)給定容量創(chuàng)建數(shù)組
            allocArrays(n);

            mSize--;
            if (index > 0) { // index 大于 0所计,則將舊數(shù)組中索引在 0-index 的 hash 對應(yīng)內(nèi)容復(fù)制到新的數(shù)組柠辞,不包含 index 位置
                System.arraycopy(ohashes, 0, mHashes, 0, index);
                System.arraycopy(oarray, 0, mArray, 0, index << 1);
            }
            
            // index 小于 mSize 說明,移除的 index 位置的值不是在數(shù)組中的最后主胧,此時(shí)需要將 index 以后的值同樣添加到新的數(shù)組中
            if (index < mSize) {
                System.arraycopy(ohashes, index + 1, mHashes, index, mSize - index);
                System.arraycopy(oarray, (index + 1) << 1, mArray, index << 1,
                        (mSize - index) << 1);
            }
        } else { // 不需要壓縮時(shí)叭首,直接將 index 以后的內(nèi)容全部左移一個(gè)單位习勤,再將最后一個(gè)鍵值對置空即可
            mSize--;
            if (index < mSize) {
                System.arraycopy(mHashes, index + 1, mHashes, index, mSize - index);
                System.arraycopy(mArray, (index + 1) << 1, mArray, index << 1,
                        (mSize - index) << 1);
            }
            mArray[mSize << 1] = null;
            mArray[(mSize << 1) + 1] = null;
        }
    }
    
    // 返回移除的 value 值
    return (V)old;
}

移除對應(yīng)位置鍵值對的過程中還對數(shù)組做了縮容處理,可以節(jié)省內(nèi)存焙格,并提升執(zhí)行查詢功能速度

六图毕、其他方法

/**
 * 清空 Map
 */
public void clear() {
    if (mSize != 0) {
        freeArrays(mHashes, mArray, mSize);
        mHashes = ContainerHelpers.EMPTY_INTS;
        mArray = ContainerHelpers.EMPTY_OBJECTS;
        mSize = 0;
    }
}

/**
 * 獲取指定 hash 數(shù)組中位置對應(yīng)的鍵
 */
public K keyAt(int index) {
    return (K)mArray[index << 1];
}

/**
 * 獲取指定 hash 數(shù)組中位置對應(yīng)的值
 */
public V valueAt(int index) {
    return (V)mArray[(index << 1) + 1];
}

/**
 * 設(shè)置 hash 數(shù)組中位置對應(yīng)的值為指定的 value
 */
public V setValueAt(int index, V value) {
    index = (index << 1) + 1;
    V old = (V)mArray[index];
    mArray[index] = value;
    return old;
}

/**
 * 判斷 Map 是否為空
 */
public boolean isEmpty() {
    return mSize <= 0;
}

/**
 * 獲取鍵值對數(shù)量
 */
public int size() {
    return mSize;
}

/**
 * equals 方法
 * 如果要對比的 object 不是一個(gè) Map 或者不是一個(gè) SimpleArrayMap 則直接返回 false ,鍵值對數(shù)量不想同時(shí)也直接返回 false
 * 如果以上對比通過眷唉,則會(huì)對比每一個(gè)鍵值對都會(huì)對比吴旋,如果鍵值對都相同則返回 true,否則返回 false
 */
public boolean equals(Object object) {
    if (this == object) {
        return true;
    }
    if (object instanceof SimpleArrayMap) {
        SimpleArrayMap<?, ?> map = (SimpleArrayMap<?, ?>) object;
        if (size() != map.size()) {
            return false;
        }

        try {
            // 遍歷當(dāng)前數(shù)組厢破,對比每一個(gè)鍵值對
            for (int i=0; i<mSize; i++) {
                K key = keyAt(i);
                V mine = valueAt(i);
                Object theirs = map.get(key);
                if (mine == null) {
                    if (theirs != null || !map.containsKey(key)) {
                        return false;
                    }
                } else if (!mine.equals(theirs)) {
                    return false;
                }
            }
        } catch (NullPointerException ignored) {
            return false;
        } catch (ClassCastException ignored) {
            return false;
        }
        return true;
    } else if (object instanceof Map) {
        Map<?, ?> map = (Map<?, ?>) object;
        if (size() != map.size()) {
            return false;
        }

        try {
            // 遍歷當(dāng)前數(shù)組荣瑟,對比每一個(gè)鍵值對
            for (int i=0; i<mSize; i++) {
                K key = keyAt(i);
                V mine = valueAt(i);
                Object theirs = map.get(key);
                if (mine == null) {
                    if (theirs != null || !map.containsKey(key)) {
                        return false;
                    }
                } else if (!mine.equals(theirs)) {
                    return false;
                }
            }
        } catch (NullPointerException ignored) {
            return false;
        } catch (ClassCastException ignored) {
            return false;
        }
        return true;
    }
    return false;
}


public int hashCode() {
    final int[] hashes = mHashes;
    final Object[] array = mArray;
    int result = 0;
    for (int i = 0, v = 1, s = mSize; i < s; i++, v+=2) {
        Object value = array[v];
        result += hashes[i] ^ (value == null ? 0 : value.hashCode());
    }
    return result;
}

/**
 * 打印所有鍵值對
 */
public String toString() {
    if (isEmpty()) {
        return "{}";
    }

    StringBuilder buffer = new StringBuilder(mSize * 28);
    buffer.append('{');
    for (int i=0; i<mSize; i++) { 
        if (i > 0) {
            buffer.append(", ");
        }
        Object key = keyAt(i);
        if (key != this) {
            buffer.append(key);
        } else {
            buffer.append("(this Map)");
        }
        buffer.append('=');
        Object value = valueAt(i);
        if (value != this) {
            buffer.append(value);
        } else {
            buffer.append("(this Map)");
        }
    }
    buffer.append('}');
    return buffer.toString();
}

七、總結(jié)

到這里整個(gè) SimpleArrayMap 的源碼就分析完了摩泪,重點(diǎn)要掌握的是由兩個(gè)數(shù)組實(shí)現(xiàn) SimpleArrayMap 的這種數(shù)據(jù)結(jié)構(gòu)笆焰,相比較 HashMap ,SimpleArrayMap 避免了每個(gè)鍵值對都使用 Entry 來包裝见坑,并且在查詢時(shí)如果數(shù)據(jù)量不大時(shí)使用二分法速度更快嚷掠。并且 SimpleArrayMap 每次 remove 操作時(shí)都會(huì)做縮容操作,這樣可以降低內(nèi)存使用荞驴,并且進(jìn)一步提升查找時(shí)的時(shí)間消耗不皆。所以在 Android 開發(fā)中,如果鍵值對的數(shù)量級較小情況下使用 SimpleArrayMap 虧更加高效熊楼!

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末霹娄,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子鲫骗,更是在濱河造成了極大的恐慌犬耻,老刑警劉巖,帶你破解...
    沈念sama閱讀 212,718評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件执泰,死亡現(xiàn)場離奇詭異枕磁,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)术吝,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,683評論 3 385
  • 文/潘曉璐 我一進(jìn)店門计济,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人排苍,你說我怎么就攤上這事沦寂。” “怎么了纪岁?”我有些...
    開封第一講書人閱讀 158,207評論 0 348
  • 文/不壞的土叔 我叫張陵凑队,是天一觀的道長则果。 經(jīng)常有香客問我幔翰,道長漩氨,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,755評論 1 284
  • 正文 為了忘掉前任遗增,我火速辦了婚禮叫惊,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘做修。我一直安慰自己霍狰,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,862評論 6 386
  • 文/花漫 我一把揭開白布饰及。 她就那樣靜靜地躺著蔗坯,像睡著了一般。 火紅的嫁衣襯著肌膚如雪燎含。 梳的紋絲不亂的頭發(fā)上宾濒,一...
    開封第一講書人閱讀 50,050評論 1 291
  • 那天,我揣著相機(jī)與錄音屏箍,去河邊找鬼绘梦。 笑死,一個(gè)胖子當(dāng)著我的面吹牛赴魁,可吹牛的內(nèi)容都是我干的卸奉。 我是一名探鬼主播,決...
    沈念sama閱讀 39,136評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼颖御,長吁一口氣:“原來是場噩夢啊……” “哼榄棵!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起潘拱,我...
    開封第一講書人閱讀 37,882評論 0 268
  • 序言:老撾萬榮一對情侶失蹤秉继,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后泽铛,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體尚辑,經(jīng)...
    沈念sama閱讀 44,330評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,651評論 2 327
  • 正文 我和宋清朗相戀三年盔腔,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了杠茬。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,789評論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡弛随,死狀恐怖瓢喉,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情舀透,我是刑警寧澤栓票,帶...
    沈念sama閱讀 34,477評論 4 333
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響走贪,放射性物質(zhì)發(fā)生泄漏佛猛。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,135評論 3 317
  • 文/蒙蒙 一坠狡、第九天 我趴在偏房一處隱蔽的房頂上張望继找。 院中可真熱鬧,春花似錦逃沿、人聲如沸婴渡。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,864評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽边臼。三九已至,卻和暖如春假消,著一層夾襖步出監(jiān)牢的瞬間硼瓣,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,099評論 1 267
  • 我被黑心中介騙來泰國打工置谦, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留堂鲤,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,598評論 2 362
  • 正文 我出身青樓媒峡,卻偏偏與公主長得像瘟栖,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子谅阿,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,697評論 2 351

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