【Java容器源碼系列】集合應(yīng)用總結(jié):迭代器&批量操作&線程安全問題

下面列出了所有集合的類圖:


圖片描述
  • 每個(gè)接口做的事情非常明確兵拢,比如 Serializable眨攘,只負(fù)責(zé)序列化,Cloneable 只負(fù)責(zé)拷貝讽营,Map 只負(fù)責(zé)定義 Map 的接口,整個(gè)圖看起來(lái)雖然接口眾多叁巨,但職責(zé)都很清晰;
  • 復(fù)雜功能通過(guò)接口的繼承來(lái)實(shí)現(xiàn)呐籽,比如 ArrayList 通過(guò)實(shí)現(xiàn)了 Serializable锋勺、Cloneable、RandomAccess狡蝶、AbstractList庶橱、List 等接口,從而擁有了序列化贪惹、拷貝苏章、對(duì)數(shù)組各種操作定義等各種功能;
  • 上述類圖只能看見繼承的關(guān)系奏瞬,組合的關(guān)系還看不出來(lái)枫绅,比如說(shuō) Set 組合封裝 Map 的底層能力等。

上述設(shè)計(jì)的最大好處是硼端,每個(gè)接口能力職責(zé)單一并淋,眾多的接口變成了接口能力的積累,假設(shè)我們想再實(shí)現(xiàn)一個(gè)數(shù)據(jù)結(jié)構(gòu)類珍昨,我們就可以從這些已有的能力接口中县耽,挑選出能滿足需求的能力接口,進(jìn)行一些簡(jiǎn)單的組裝镣典,從而加快開發(fā)速度兔毙。

這種思想在平時(shí)的工作中也經(jīng)常被使用,我們會(huì)把一些通用的代碼塊抽象出來(lái)兄春,沉淀成代碼塊池澎剥,碰到不同的場(chǎng)景的時(shí)候,我們就從代碼塊池中赶舆,把我們需要的代碼塊提取出來(lái)肴裙,進(jìn)行簡(jiǎn)單的編排和組裝,從而實(shí)現(xiàn)我們需要的場(chǎng)景功能涌乳。

1.集合迭代

在講具體迭代方式之前蜻懦,先來(lái)看一下迭代的頂層接口:Iterable。在看具體源碼前先明白兩個(gè)問題:

  1. Iterable有什么用呢夕晓?實(shí)現(xiàn)了Itrable接口就能進(jìn)行迭代(遍歷)操作宛乃。
  2. 為什么我在ArrayList中沒有看見它實(shí)現(xiàn)Iterable呢?從上面的類圖中可以看到,集合的頂級(jí)接口Collection實(shí)現(xiàn)了Iterable征炼,但這里注意一點(diǎn)析既,Map并沒有實(shí)現(xiàn)Iterable(map的迭代第二部分再講解)。
public interface Iterable<T> {
    Iterator<T> iterator()
        
    default void forEach(Consumer<? super T> action) {
        Objects.requireNonNull(action);
        for (T t : this) {
            action.accept(t);
        }
    }
    
    default Spliterator<T> spliterator() {
        return Spliterators.spliteratorUnknownSize(iterator(), 0);
    }
}

從接口中我們看到遍歷的手段有兩種:

  1. 迭代器Iterator谆奥,可以實(shí)現(xiàn)多個(gè)Iterator進(jìn)行多種方法的迭代(前序眼坏,后序...)
  2. forEach方法,一般由具體子類重寫該方法

這里注意一點(diǎn)酸些,Iterator 和 forEach 都需要要進(jìn)行迭代的類自行實(shí)現(xiàn)宰译。

1.1 方式一:迭代器Iterator

常由內(nèi)部類實(shí)現(xiàn),iterator方法返回魄懂,一般用于要對(duì)集合進(jìn)行刪除的情景

  • 迭代器是一種設(shè)計(jì)模式沿侈,封裝了對(duì)集合的遍歷,使得不用了解集合的內(nèi)部細(xì)節(jié)市栗,就可以用同樣的方式遍歷不同的集合
  • 迭代器不允許使用集合方法進(jìn)行集合增刪缀拭,但是可以對(duì)集合的元素操作(如set()),還可以使用迭代器的remove()
    • 不能使用集合的 put 和 remove 方法
    • 可用于集合元素屬性的修改:set方法
    • remove操作:是Iterator的remove方法

這里特別注意一點(diǎn)填帽,一定要在next()后使用蛛淋,比如刪除第一個(gè)元素,要先next然后才能remove

public interface Iterator<E> {
   
    // 每次next之前篡腌,先調(diào)用此方法探測(cè)是否迭代到終點(diǎn)
    boolean hasNext();  
    
    // 返回當(dāng)前迭代元素 铣鹏,同時(shí),迭代游標(biāo)后移
    E next();           
              
     /*刪除最近一次已近迭代出出去的那個(gè)元素哀蘑。
     只有當(dāng)next執(zhí)行完后诚卸,才能調(diào)用remove函數(shù)。
     比如你要?jiǎng)h除第一個(gè)元素绘迁,不能直接調(diào)用 remove()   而要先next一下( );
     在沒有先調(diào)用next 就調(diào)用remove方法是會(huì)拋出異常的合溺。
     這個(gè)和MySQL中的ResultSet很類似
    */
    void remove() 
    {
        throw new UnsupportedOperationException("remove");
    }
}
  • 迭代器使用示例:
// iterator是集合的自己Iterator構(gòu)造方法
Iterator it = list.iterator();
while(it.hasNext) {
    it.next();
}

1.2 方式二:forEach方法

本質(zhì)是對(duì)for循環(huán)的封裝,配合lamada使用缀台,一般用于修改集合對(duì)象屬性的情景

// ArrayList.forEach()
@Override
public void forEach(Consumer<? super E> action) {
  // 判斷非空
  Objects.requireNonNull(action);
  // modCount的原始值被拷貝
  final int expectedModCount = modCount;
  final E[] elementData = (E[]) this.elementData;
  final int size = this.size;
  // 每次循環(huán)都會(huì)判斷數(shù)組有沒有被修改棠赛,一旦被修改,停止循環(huán)
  for (int i=0; modCount == expectedModCount && i < size; i++) {
    // 執(zhí)行循環(huán)內(nèi)容膛腐,action 代表我們要干的事情
    action.accept(elementData[i]);
  }
  // 數(shù)組如果被修改了睛约,拋異常
  if (modCount != expectedModCount) {
    throw new ConcurrentModificationException();
  }
}
  • 因?yàn)樵贗terable接口中default修飾,所以必須自身實(shí)現(xiàn)而子類不一定要重寫哲身,在jdk8時(shí)所有集合都實(shí)現(xiàn)了forEach方法
  • forEach:
    • 修改對(duì)象屬性:通過(guò)set方法
    • 不能進(jìn)行增刪操作:modCount
  • 使用示例:
list.forEach(l -> {
    l.setName("zs");
    l.setAge(18);
})

1.3 方式三:增強(qiáng)for循環(huán)

本質(zhì)是對(duì)Iterator的簡(jiǎn)化與封裝辩涝,一般用于只遍歷集合的情況

  • 增強(qiáng)for循環(huán):
    • 可以用于集合中元素屬性值的修改:set方法
    • 但不能對(duì)集合新增或者刪除:modCount控制
  • 使用示例:
public static void main(String[] args) {
        ArrayList<Person> people = new ArrayList<>();
        people.add(new Person("zs")); 
        people.add(new Person("lisi"));
        
        // 調(diào)用增強(qiáng)for循環(huán)
        for(Person p: people) {
            // 通過(guò)set修改對(duì)象屬性值,成功
            p.setName("abc");
        }
        System.out.println(people); // abc,abc
}

2.Map迭代

從文章首部的類圖中可以看到勘天,map并未實(shí)現(xiàn)Itrable接口怔揩,那map該如何進(jìn)行迭代呢捉邢?

  1. 通過(guò)set的Iterator進(jìn)行迭代

  2. 最高層Map接口定義了forEach方法

2.1 方式一:Set.iterator

Map 對(duì) key、value 和 entity(節(jié)點(diǎn)) 都提供了以 Set 為基礎(chǔ)的迭代器商膊。這些迭代器可以通過(guò)map.~Set().iterator()進(jìn)行獲取

  • 迭代key:HashMap --keySet()--> KeySet --iterator()--> KeyIterator
  • 迭代value:HashMap --values()--> Values--iterator()--> ValueIterator
  • 迭代key:HashMap --entrySet()--> EntrySet--iterator()--> EntryIterator

雖然是不同的迭代器伏伐,但是它們本質(zhì)上卻沒有區(qū)別,主要體現(xiàn)在以下兩點(diǎn):

  1. 都繼承了HashIterator晕拆,即擁有HashIterator的所有方法
abstract class HashIterator {
        Node<K,V> next;        // next entry to return
        Node<K,V> current;     // current entry
        int expectedModCount;  // for fast-fail
        int index;             // current slot

        HashIterator() {
            expectedModCount = modCount;
            Node<K,V>[] t = table;
            current = next = null;
            index = 0;
            if (t != null && size > 0) { // advance to first entry
                do {} while (index < t.length && (next = t[index++]) == null);
            }
        }
    ......
}
  1. 都只有一個(gè)方法:next()藐翎,而且里面調(diào)用的都是 HashIterator.nextNode(),只不過(guò)最后在node中取值不同
final class KeyIterator extends HashIterator
    implements Iterator<K> {
    public final K next() { return nextNode().key; } // 調(diào)用父類的nextNode方法实幕,返回node的key
}

final class ValueIterator extends HashIterator
    implements Iterator<V> {
    public final V next() { return nextNode().value; } // 調(diào)用父類的nextNode方法吝镣,返回node的value
}

final class EntryIterator extends HashIterator
    implements Iterator<Map.Entry<K,V>> {
    public final Map.Entry<K,V> next() { return nextNode(); }  // 調(diào)用父類的nextNode方法,返回node
}

使用示例:

Iterator<Map.Entry<String,String>> it = map.entrySet().iterator
while (it.hasNext()) {
    Map.Entry<String,String>  me = it.next();
    // 獲取key
    me.getkey();
    // 獲取value
    me.getValue();
}

2.2 方式二:Map.forEach

Map中定義的forEach茬缩,default修飾赤惊,實(shí)際上也是調(diào)用entrySet

default void forEach(BiConsumer<? super K, ? super V> action) {
        Objects.requireNonNull(action);
        for (Map.Entry<K, V> entry : entrySet()) {
            K k;
            V v;
            try {
                k = entry.getKey();
                v = entry.getValue();
            } catch(IllegalStateException ise) {
                // this usually means the entry is no longer in the map.
                throw new ConcurrentModificationException(ise);
            }
            action.accept(k, v);
        }
}

HashMap的forEach方法源碼:

    @Override
    public void forEach(BiConsumer<? super K, ? super V> action) {
        Node<K,V>[] tab;
        if (action == null)
            throw new NullPointerException();
        if (size > 0 && (tab = table) != null) {
            int mc = modCount;
            for (int i = 0; i < tab.length; ++i) {
                for (Node<K,V> e = tab[i]; e != null; e = e.next)
                    action.accept(e.key, e.value);
            }
            if (modCount != mc)
                throw new ConcurrentModificationException();
       }
 }

3.批量操作

3.1 批量新增

下面列出 ArrayList.addAll 方法的源碼:

public boolean addAll(Collection<? extends E> c) {
  Object[] a = c.toArray();
  int numNew = a.length;
  // 確保容量充足吼旧,整個(gè)過(guò)程只會(huì)擴(kuò)容一次
  ensureCapacityInternal(size + numNew); 
  // 進(jìn)行數(shù)組的拷貝
  System.arraycopy(a, 0, elementData, size, numNew);
  size += numNew;
  return numNew != 0;
}

我們可以看到凰锡,整個(gè)批量新增的過(guò)程中,只擴(kuò)容了一次圈暗,HashMap 的 putAll 方法也是如此掂为,整個(gè)新增過(guò)程只會(huì)擴(kuò)容一次,大大縮短了批量新增的時(shí)間员串,提高了性能勇哗。

所以當(dāng)碰到集合批量拷貝,批量新增場(chǎng)景寸齐,要提高新增性能的時(shí)候 欲诺,就可以從目標(biāo)集合初始化方面入手。

這里也提醒了我們渺鹦,在容器初始化的時(shí)候扰法,最好能給容器賦上初始值,這樣可以防止在 put 的過(guò)程中不斷的擴(kuò)容毅厚,從而縮短時(shí)間塞颁,上章 HashSet 的源碼演示了給 HashMap 賦初始值的公式為:取括號(hào)內(nèi)兩者的最大值(期望的值/0.75+1,默認(rèn)值 16)吸耿。

使用示例:

在 List 和 Map 大量數(shù)據(jù)新增的時(shí)候祠锣,我們不要使用 for 循環(huán) + add/put 方法新增,這樣子會(huì)有很大的擴(kuò)容成本咽安,我們應(yīng)該盡量使用 addAll 和 putAll 方法進(jìn)行新增伴网,下面以 ArrayList 為例寫了一個(gè) demo 如下,演示了兩種方案的性能對(duì)比:

@Test
public void testBatchInsert(){
  // 準(zhǔn)備拷貝數(shù)據(jù)
  ArrayList<Integer> list = new ArrayList<>();
  for(int i=0;i<3000000;i++){
    list.add(i);
  }

  // for 循環(huán) + add
  ArrayList<Integer> list2 = new ArrayList<>();
  long start1 = System.currentTimeMillis();
  for(int i=0;i<list.size();i++){
    list2.add(list.get(i));
  }
  log.info("單個(gè) for 循環(huán)新增 300 w 個(gè)妆棒,耗時(shí){}",System.currentTimeMillis()-start1);

  // 批量新增
  ArrayList<Integer> list3 = new ArrayList<>();
  long start2 = System.currentTimeMillis();
  list3.addAll(list);
  log.info("批量新增 300 w 個(gè)是偷,耗時(shí){}",System.currentTimeMillis()-start2);
}

最后打印出來(lái)的日志為:

16:52:59.865 [main] INFO demo.one.ArrayListDemo - 單個(gè) for 循環(huán)新增 300 w 個(gè)拳氢,耗時(shí)1518

16:52:59.880 [main] INFO demo.one.ArrayListDemo - 批量新增 300 w 個(gè),耗時(shí)8

可以看到蛋铆,批量新增方法性能是單個(gè)新增方法性能的 189 倍馋评,主要原因在于批量新增,只會(huì)擴(kuò)容一次刺啦,大大縮短了運(yùn)行時(shí)間留特,而單個(gè)新增,每次到達(dá)擴(kuò)容閥值時(shí)玛瘸,都會(huì)進(jìn)行擴(kuò)容蜕青,在整個(gè)過(guò)程中就會(huì)不斷的擴(kuò)容,浪費(fèi)了很多時(shí)間

3.2 批量刪除

批量刪除 ArrayList 提供了 removeAll 的方法糊渊,HashMap 沒有提供批量刪除的方法右核,我們一起來(lái)看下 removeAll 的源碼實(shí)現(xiàn),是如何提高性能的:

// 批量刪除渺绒,removeAll 方法底層調(diào)用的是 batchRemove 方法
// complement 參數(shù)默認(rèn)是 false,false 的意思是數(shù)組中不包含 c 中數(shù)據(jù)的節(jié)點(diǎn)往頭移動(dòng)
// true 意思是數(shù)組中包含 c 中數(shù)據(jù)的節(jié)點(diǎn)往頭移動(dòng)贺喝,這個(gè)是根據(jù)你要?jiǎng)h除數(shù)據(jù)和原數(shù)組大小的比例來(lái)決定的
// 如果你要?jiǎng)h除的數(shù)據(jù)很多,選擇 false 性能更好宗兼,當(dāng)然 removeAll 方法默認(rèn)就是 false躏鱼。
private boolean batchRemove(Collection<?> c, boolean complement) {
  final Object[] elementData = this.elementData;
  // r 表示當(dāng)前循環(huán)的位置、w 位置之前都是不需要被刪除的數(shù)據(jù)殷绍,w 位置之后都是需要被刪除的數(shù)據(jù)
  int r = 0, w = 0;
  boolean modified = false;
    
  try {
    // 從 0 位置開始判斷染苛,當(dāng)前數(shù)組中元素是不是要被刪除的元素,不是的話移到數(shù)組頭
    for (; r < size; r++)
      if (c.contains(elementData[r]) == complement)
        elementData[w++] = elementData[r];
  } finally {
    // r 和 size 不等主到,說(shuō)明在 try 過(guò)程中發(fā)生了異常茶行,在 r 處斷開
    // 把 r 位置之后的數(shù)組移動(dòng)到 w 位置之后(r 位置之后的數(shù)組數(shù)據(jù)都是沒有判斷過(guò)的數(shù)據(jù),這樣不會(huì)影響沒有判斷
    //  的數(shù)據(jù)登钥,判斷過(guò)的數(shù)據(jù)可以被刪除)
    if (r != size) {
      System.arraycopy(elementData, r,
                       elementData, w,
                       size - r);
      w += size - r;
    }
      
    // w != size 說(shuō)明數(shù)組中是有數(shù)據(jù)需要被刪除的
    // 如果 w畔师、size 相等,說(shuō)明沒有數(shù)據(jù)需要被刪除
    if (w != size) {
      // w 之后都是需要?jiǎng)h除的數(shù)據(jù)怔鳖,賦值為空茉唉,幫助 gc。
      for (int i = w; i < size; i++)
        elementData[i] = null;
      modCount += size - w;
      size = w;
      modified = true;
    }
  }
  return modified;
}

我們看到 ArrayList 在批量刪除時(shí)结执,如果程序執(zhí)行正常度陆,只有一次 for 循環(huán),如果程序執(zhí)行異常献幔,才會(huì)加一次拷貝懂傀,而單個(gè) remove 方法,每次執(zhí)行的時(shí)候都會(huì)進(jìn)行數(shù)組的拷貝(當(dāng)刪除的元素正好是數(shù)組最后一個(gè)元素時(shí)除外)蜡感,當(dāng)數(shù)組越大蹬蚁,需要?jiǎng)h除的數(shù)據(jù)越多時(shí)恃泪,批量刪除的性能會(huì)越差,所以在 ArrayList 批量刪除時(shí)犀斋,強(qiáng)烈建議使用 removeAll 方法進(jìn)行刪除贝乎。

4.線程安全問題

我們說(shuō)集合都是非線程安全的,這里說(shuō)的非線程安全指的是集合類作為共享變量叽粹,被多線程讀寫的時(shí)候览效,才是不安全的,如果要實(shí)現(xiàn)線程安全的集合虫几,在類注釋中锤灿,JDK 統(tǒng)一推薦我們使用 Collections.synchronized* 類, Collections 幫我們實(shí)現(xiàn)了 List辆脸、Set但校、Map 對(duì)應(yīng)的線程安全的方法, 如下圖:


圖片描述

圖中實(shí)現(xiàn)了各種集合類型的線程安全的方法啡氢,我們以 synchronizedList 為例状囱,從源碼上來(lái)看下,Collections 是如何實(shí)現(xiàn)線程安全的:

// mutex 就是我們需要鎖住的對(duì)象
final Object mutex;  

// 這些synchronized~~都是Collections的靜態(tài)內(nèi)部類
static class SynchronizedList<E> extends SynchronizedCollection<E> implements List<E> {
    
        private static final long serialVersionUID = -7754090372962971524L;
        // 通過(guò)組合的方式空执,傳入需要保證線程安全的類(List)
        // Collection.synchronizedList(list)
        final List<E> list;
        SynchronizedList(List<E> list, Object mutex) {
            super(list, mutex);
            this.list = list;
        }
        
       // 我們可以看到浪箭,List 的所有操作都使用了 synchronized 關(guān)鍵字穗椅,來(lái)進(jìn)行加鎖
       // synchronized 是一種悲觀鎖辨绊,能夠保證同一時(shí)刻,只能有一個(gè)線程能夠獲得鎖
        public E get(int index) {
            synchronized (mutex) {return list.get(index);}
        }
        public E set(int index, E element) {
            synchronized (mutex) {return list.set(index, element);}
        }
        public void add(int index, E element) {
            synchronized (mutex) {list.add(index, element);}
        }
…………
}      

從源碼中我們可以看到 Collections 是通過(guò) synchronized 關(guān)鍵字給 List 操作數(shù)組的方法加上鎖匹表,來(lái)實(shí)現(xiàn)線程安全的门坷。

5.兩點(diǎn)注意

在文章的最后,再提出使用集合時(shí)的兩點(diǎn)注意:

  1. 重寫equals & hashcode
    當(dāng)集合的元素是自定義類時(shí)袍镀,自定義類強(qiáng)制實(shí)現(xiàn) equals 和 hashCode 方法默蚌,并且兩個(gè)都要實(shí)現(xiàn)。因?yàn)樵诩现形郏?TreeMap 和 TreeSet 是通過(guò)比較器比較元素大小外绸吸,其余的集合類在判斷索引位置和相等時(shí),都會(huì)使用到 equals 和 hashCode 方法设江,這個(gè)在之前的源碼解析中锦茁,我們有說(shuō)到,所以當(dāng)集合的元素是自定義類時(shí)叉存,我們強(qiáng)烈建議覆寫 equals 和 hashCode 方法码俩,我們可以直接使用 IDEA 工具覆寫這兩個(gè)方法,非常方便

  2. 迭代刪除
    所有集合類歼捏,在 for 循環(huán)進(jìn)行刪除時(shí)稿存,如果直接使用集合類的 remove 方法進(jìn)行刪除笨篷,都會(huì)快速失敗,報(bào) ConcurrentModificationException 的錯(cuò)誤瓣履,所以在任意循環(huán)刪除的場(chǎng)景下率翅,都建議使用迭代器進(jìn)行刪除;

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末袖迎,一起剝皮案震驚了整個(gè)濱河市安聘,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌瓢棒,老刑警劉巖浴韭,帶你破解...
    沈念sama閱讀 218,941評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異脯宿,居然都是意外死亡念颈,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,397評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門连霉,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)榴芳,“玉大人,你說(shuō)我怎么就攤上這事跺撼】吒校” “怎么了?”我有些...
    開封第一講書人閱讀 165,345評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵歉井,是天一觀的道長(zhǎng)柿祈。 經(jīng)常有香客問我,道長(zhǎng)哩至,這世上最難降的妖魔是什么躏嚎? 我笑而不...
    開封第一講書人閱讀 58,851評(píng)論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮菩貌,結(jié)果婚禮上卢佣,老公的妹妹穿的比我還像新娘。我一直安慰自己箭阶,他們只是感情好虚茶,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,868評(píng)論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著仇参,像睡著了一般嘹叫。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上冈敛,一...
    開封第一講書人閱讀 51,688評(píng)論 1 305
  • 那天待笑,我揣著相機(jī)與錄音,去河邊找鬼抓谴。 笑死暮蹂,一個(gè)胖子當(dāng)著我的面吹牛寞缝,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播仰泻,決...
    沈念sama閱讀 40,414評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼荆陆,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了集侯?” 一聲冷哼從身側(cè)響起被啼,我...
    開封第一講書人閱讀 39,319評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎棠枉,沒想到半個(gè)月后浓体,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,775評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡辈讶,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,945評(píng)論 3 336
  • 正文 我和宋清朗相戀三年命浴,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片贱除。...
    茶點(diǎn)故事閱讀 40,096評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡生闲,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出月幌,到底是詐尸還是另有隱情碍讯,我是刑警寧澤,帶...
    沈念sama閱讀 35,789評(píng)論 5 346
  • 正文 年R本政府宣布扯躺,位于F島的核電站捉兴,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏缅帘。R本人自食惡果不足惜轴术,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,437評(píng)論 3 331
  • 文/蒙蒙 一难衰、第九天 我趴在偏房一處隱蔽的房頂上張望钦无。 院中可真熱鬧,春花似錦盖袭、人聲如沸失暂。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,993評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)弟塞。三九已至,卻和暖如春拙已,著一層夾襖步出監(jiān)牢的瞬間决记,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,107評(píng)論 1 271
  • 我被黑心中介騙來(lái)泰國(guó)打工倍踪, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留系宫,地道東北人索昂。 一個(gè)月前我還...
    沈念sama閱讀 48,308評(píng)論 3 372
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像扩借,于是被迫代替她去往敵國(guó)和親椒惨。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,037評(píng)論 2 355