Java 集合框架_List

Java 集合框架系列

Java 集合框架_開篇
Java 集合框架_List
Java 集合框架_ArrayList
Java 集合框架_LinkedList

上一章講述了頂層接口Collection以及抽樣類AbstractCollection。但是Collection接口集合有個缺陷,它只提供遍歷集合,添加,刪除的方法,沒有提供讓我們快速找到集合中某個元素的方法。這個就要使用List集合了。

一. List 接口

public interface List<E> extends Collection<E> {}

List接口繼承自Collection接口,它提供一種索引概念,就像數組下標一樣,讓我們可以快速找到對應索引位置的元素,也可以在索引位置添加,刪除,替換對應元素。
對比Collection接口,想一下List接口擁有的方法,肯定與索引有關系。

  1. 添加元素:
    // 繼承自Collection接口方法
    boolean add(E e);
    boolean addAll(Collection<? extends E> c);

    // List接口新加方法,在指定索引位置添加元素
    void add(int index, E element);
    boolean addAll(int index, Collection<? extends E> c);
  1. 刪除元素
    // 繼承自Collection接口方法
    boolean remove(Object o);
    boolean removeAll(Collection<?> c);
    boolean retainAll(Collection<?> c);
    void clear();

    // List接口新加方法,刪除指定索引位置元素
    E remove(int index);
  1. 替換元素
    // List接口新加方法, 替換指定索引位置的元素
    E set(int index, E element);
  1. 查詢操作
     // 繼承自Collection接口方法
    boolean contains(Object o);
    boolean containsAll(Collection<?> c);
    Iterator<E> iterator();

    // List接口新加方法, 根據索引查找對應元素,根據元素查找索引位置
    E get(int index);
    int indexOf(Object o);
    int lastIndexOf(Object o);

    // List接口新加方法, 返回功能性更強的迭代器ListIterator
    ListIterator<E> listIterator();
    ListIterator<E> listIterator(int index);
  1. 其他重要方法
    int size();
    boolean isEmpty();
    Object[] toArray();
    <T> T[] toArray(T[] a);

    // List接口新加方法,返回fromIndex位置到toIndex位置的子集合,
    // (包括fromIndex,不包括toIndex)
    List<E> subList(int fromIndex, int toIndex);
  1. 整體預覽
public interface List<E> extends Collection<E> {

    // 添加元素
    // 繼承自Collection接口方法
    boolean add(E e);

    boolean addAll(Collection<? extends E> c);

    // List接口新加方法,在指定索引位置添加元素
    void add(int index, E element);

    boolean addAll(int index, Collection<? extends E> c);


    // 刪除元素
    // 繼承自Collection接口方法
    boolean remove(Object o);

    boolean removeAll(Collection<?> c);

    boolean retainAll(Collection<?> c);

    void clear();

    // List接口新加方法,刪除指定索引位置元素
    E remove(int index);



    // 更新元素
    // List接口新加方法, 替換指定索引位置的元素
    E set(int index, E element);


    // 查詢方法
    // 繼承自Collection接口方法
    boolean contains(Object o);

    boolean containsAll(Collection<?> c);

    Iterator<E> iterator();

    // List接口新加方法, 根據索引查找對應元素,根據元素查找索引位置
    E get(int index);

    int indexOf(Object o);

    int lastIndexOf(Object o);

    // List接口新加方法, 返回功能性更強的迭代器ListIterator
    ListIterator<E> listIterator();

    ListIterator<E> listIterator(int index);

    
    int size();

    boolean isEmpty();

    Object[] toArray();

    <T> T[] toArray(T[] a);

    // List接口新加方法,返回fromIndex位置到toIndex位置的子集合,(包括fromIndex,不包括toIndex)
    List<E> subList(int fromIndex, int toIndex);


    default void replaceAll(UnaryOperator<E> operator) {
        Objects.requireNonNull(operator);
        final ListIterator<E> li = this.listIterator();
        while (li.hasNext()) {
            li.set(operator.apply(li.next()));
        }
    }

    @SuppressWarnings({"unchecked", "rawtypes"})
    default void sort(Comparator<? super E> c) {
        Object[] a = this.toArray();
        Arrays.sort(a, (Comparator) c);
        ListIterator<E> i = this.listIterator();
        for (Object e : a) {
            i.next();
            i.set((E) e);
        }
    }

    boolean equals(Object o);

    int hashCode();

    @Override
    default Spliterator<E> spliterator() {
        return Spliterators.spliterator(this, Spliterator.ORDERED);
    }
}

二. ListIterator接口

在List接口中,我們發現它返回了一個新的迭代器類型ListIterator。這個又是做什么用的呢?
我們知道Iterator接口有三個方法(forEachRemaining請忽略不計)。通過hasNext和next方法來遍歷數組,通過remove方法來刪除數組。

你會發現它沒有添加和替換的方法,那是因為不同集合根據它數據結構的不同,添加和替換的操作不好界定,所以就交給它們各自獨特的迭代器來實現,比如說這里的ListIterator。而最頂層的迭代器只提供這個三個方法。

我們知道List集合是可以通過索引查找集合中的元素,所以List集合一個連續的集合,可以查找前一個索引的元素,也可以查找后一個索引的元素,還可以添加替換元素。

public interface ListIterator<E> extends Iterator<E> {

    // 繼承自Iterator中的方法
    boolean hasNext();

    E next();

    void remove();

    // 反向遍歷集合時使用
    boolean hasPrevious();

    E previous();

    // 如果是正向遍歷集合,nextIndex返回值表示集合中下一個元素的索引位置。
    // 如果是反向遍歷集合,nextIndex返回值表示集合中當前元素的索引位置。
    int nextIndex();

    // 如果是正向遍歷集合,previousIndex返回值表示集合中當前元素的索引位置。
    // 如果是反向遍歷集合,previousIndex返回值表示集合中前一個元素的索引位置。
    int previousIndex();

    // 用元素e替換當前索引位置的元素
    void set(E e);

    // 在當前索引下一個位置添加元素e,再將索引位置加1,不遍歷新添加的元素。
    // 保證我們完整地遍歷集合中原有的元素,而使用迭代器的刪除,替換,添加操作,都不會影響本次遍歷過程。
    void add(E e);
}

這里有一點需要注意,當我們得到一個集合迭代器,進行遍歷的時候,我們有可能在遍歷過程中,用迭代器進行刪除,添加,替換操作。要保證一點,就是這些操作不影響當前迭代過程,也就是說遍歷得到的還是原來集合的數據。

三. AbstractList 抽樣類

AbstractList有一個非常重要的成員屬性modCount。

它用在多線程環境下,某個正在遍歷的集合,是否被別的線程修改了,如果是,那么就會拋出ConcurrentModificationException異常。也就是說這個異常是在迭代器中拋出的。

3.1 添加元素

    public boolean add(E e) {
        add(size(), e);
        return true;
    }
    public void add(int index, E element) {
        throw new UnsupportedOperationException();
    }

add(E e) 方法是向集合末尾添加元素。而AbstractList 默認是不可修改的集合,需要自己手動復寫add(int index, E element)方法,才能添加元素。

boolean addAll(Collection<? extends E> c)這個方法是在AbstractCollection中實現的。

   public boolean addAll(int index, Collection<? extends E> c) {
        rangeCheckForAdd(index);
        boolean modified = false;
        for (E e : c) {
            add(index++, e);
            modified = true;
        }
        return modified;
    }

   private void rangeCheckForAdd(int index) {
        if (index < 0 || index > size)
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    }

rangeCheckForAdd方法是檢查索引是否越界的。然后遍歷c集合,將每個元素添加到本集合中。

3.2 刪除元素

刪除元素的方法,大部分使用AbstractCollection類提供的實現。

   public E remove(int index) {
        throw new UnsupportedOperationException();
    }

AbstractList是個不可修改的集合,所以這里做了限制。

3.3 更新元素

    public E set(int index, E element) {
        throw new UnsupportedOperationException();
    }

理由同上。

3.4 查詢方法

boolean contains(Object o)和 boolean containsAll(Collection<?> c)方法采用AbstractCollection類提供的實現。

    public Iterator<E> iterator() {
        return new Itr();
    }
    public ListIterator<E> listIterator() {
        return listIterator(0);
    }
    public ListIterator<E> listIterator(final int index) {
        rangeCheckForAdd(index);

        return new ListItr(index);
    }

AbstractList提供了兩個迭代器子類 Itr 和 ListItr ,之后我們將來分析它。

abstract public E get(int index);

get(int index)方法強制子類必須實現。

   public int indexOf(Object o) {
        ListIterator<E> it = listIterator();
        if (o==null) {
            while (it.hasNext())
                if (it.next()==null)
                    return it.previousIndex();
        } else {
            while (it.hasNext())
                if (o.equals(it.next()))
                    return it.previousIndex();
        }
        return -1;
    }

正向查找某個元素在集合中的位置,如果沒找到就返回-1.這里就要用到ListIterator迭代器了,因為它可以集合中的位置索引。

注意當我們正向遍歷集合的時候,previousIndex()方法返回的就是當前元素的位置,而并不是前一個元素的位置。這個時候nextIndex()方法返回的是下一個元素的位置。

   public int lastIndexOf(Object o) {
        ListIterator<E> it = listIterator(size());
        if (o==null) {
            while (it.hasPrevious())
                if (it.previous()==null)
                    return it.nextIndex();
        } else {
            while (it.hasPrevious())
                if (o.equals(it.previous()))
                    return it.nextIndex();
        }
        return -1;
    }

反向查找某個元素在集合中的位置,如果沒找到就返回-1。

注意當我們反向遍歷集合的時候,nextIndex()方法返回的就是當前元素的位置,而并不是下一個元素的位置。這個時候previousIndex()方法返回的是前一個元素的位置。

3.5 其他重要方法

int size()、boolean isEmpty()、Object[] toArray()、<T> T[] toArray(T[] a)方法都采用AbstractCollection類提供的實現。

   public List<E> subList(int fromIndex, int toIndex) {
        return (this instanceof RandomAccess ?
                new RandomAccessSubList<>(this, fromIndex, toIndex) :
                new SubList<>(this, fromIndex, toIndex));
    }

AbstractList提供默認subList方法的實現。之后我們再來分析SubList和RandomAccessSubList這兩個類。

四. AbstractList的內部類 Itr

迭代器的實現一般都是某個集合的內部類,因為這樣就可以直接訪問集合的成員屬性,修改操作集合中的元素。通過Itr我們來了解怎樣簡單地實現一個迭代器。

4.1 成員屬性

       // 光標索引位置,0表示在集合開始位置(因為這是一個list集合,所以可以用索引表示開始位置)
        int cursor = 0;
        // 表示讀取到的當前元素位置
        int lastRet = -1;
        // 用來判斷原集合是否被修改,拋出ConcurrentModificationException異常。
        int expectedModCount = modCount;

4.2 遍歷集合

       final void checkForComodification() {
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
        }

如果集合中modCount的值與迭代器中expectedModCount的值不相等,就說明在迭代器期間集合被修改了,那么遍歷的數據已經失效,就拋出異常。

        public boolean hasNext() {
            return cursor != size();
        }

判斷集合中是否還有未讀取的元素,即cursor光標已經移動到最后了。

      public E next() {
            checkForComodification();
            try {
                int i = cursor;
                E next = get(i);
                lastRet = i;
                cursor = i + 1;
                return next;
            } catch (IndexOutOfBoundsException e) {
                checkForComodification();
                throw new NoSuchElementException();
            }
        }
  1. 先檢查集合有沒有沒被修改。
  2. 然后調用list集合的get(i)方法,獲取cursor光標位置的元素。
  3. 因為我們獲取了元素,就要將元素的索引cursor賦值給lastRet變量。
  4. 將cursor光標位置加1,指向下一個元素。
  5. 最后返回獲取的元素。

4.3 刪除集合中元素

     public void remove() {
            if (lastRet < 0)
                throw new IllegalStateException();
            checkForComodification();

            try {
                AbstractList.this.remove(lastRet);
                if (lastRet < cursor)
                    cursor--;
                lastRet = -1;
                expectedModCount = modCount;
            } catch (IndexOutOfBoundsException e) {
                throw new ConcurrentModificationException();
            }
        }
  1. 如果lastRet < 0表示當前位置元素無效,不能進行刪除操作,拋出異常。
  2. 調用List集合remove(lastRet)方法刪除集合當前元素。
  3. 如果lastRet < cursor為真,就讓cursor光標自減1。

刪除一個元素,cursor光標值的變化分兩種情況。正向遍歷,cursor光標指向下一個元素,這時集合刪除一個元素,原集合下一個元素移動到當前位置,所以cursor光標要減一。反向遍歷,因為是從后向前遍歷,那么刪除一個元素,對于坐標位置沒有任何影響。通過lastRet < cursor來判斷是正向遍歷還是反向遍歷。

  1. 將lastRet重置為-1,因為當前元素位置的元素已經被移除了,不能在刪除了。
  2. expectedModCount = modCount 這個很重要,因為我們調用了集合的remove方法修改了集合,所以集合的modCount值就改變了,要重新賦值,防止拋出ConcurrentModificationException異常。

五. AbstractList的內部類 ListItr

它繼承自Itr類,實現了ListIterator接口。它實現了對List集合的反向遍歷,以及添加和替換集合中元素的方法。

5.1 構造函數

        ListItr(int index) {
            cursor = index;
        }

表示從index - 1位置開始,反向遍歷集合元素。

5.2 反向遍歷集合

        public boolean hasPrevious() {
            return cursor != 0;
        }

判斷集合中是否還有未讀取的元素。當cursor==0,表示已經讀取到第一元素了,前面以及沒有元素了。

     public E previous() {
            checkForComodification();
            try {
                int i = cursor - 1;
                E previous = get(i);
                lastRet = cursor = i;
                return previous;
            } catch (IndexOutOfBoundsException e) {
                checkForComodification();
                throw new NoSuchElementException();
            }
        }
  1. 先檢查集合有沒有沒被修改。
  2. cursor - 1表示前一個元素的索引。
  3. 調用list集合的get(i)方法,獲取對應索引位置的元素。
  4. lastRet = cursor = i; 表示cursor光標就表示當前元素的索引位置
  5. 最后返回獲取的元素。

5.3 返回索引位置。

        // 如果是正向遍歷集合,nextIndex返回值表示集合中下一個元素的索引位置。
        // 如果是反向遍歷集合,nextIndex返回值表示集合中當前元素的索引位置。
        public int nextIndex() {
            return cursor;
        }

        // 如果是正向遍歷集合,previousIndex返回值表示集合中當前元素的索引位置。
        // 如果是反向遍歷集合,previousIndex返回值表示集合中前一個元素的索引位置。
        public int previousIndex() {
            return cursor-1;
        }

5.4 替換當前元素

       public void set(E e) {
            if (lastRet < 0)
                throw new IllegalStateException();
            checkForComodification();

            try {
                AbstractList.this.set(lastRet, e);
                expectedModCount = modCount;
            } catch (IndexOutOfBoundsException ex) {
                throw new ConcurrentModificationException();
            }
        }
  1. lastRet < 0 表示當前位置無效,不能更換元素,拋出異常。
  2. checkForComodification方法檢查集合有沒有沒被修改。
  3. 調用List集合的set(lastRet, e)方法,替換當前元素。
  4. 因為修改了集合,那么重新賦值expectedModCount = modCount

5.5 添加元素

       public void add(E e) {
            checkForComodification();

            try {
                int i = cursor;
                AbstractList.this.add(i, e);
                lastRet = -1;
                cursor = i + 1;
                expectedModCount = modCount;
            } catch (IndexOutOfBoundsException ex) {
                throw new ConcurrentModificationException();
            }
        }
  1. 調用checkForComodification方法檢查集合有沒有沒被修改。
  2. 調用List的add(i, e)方法,在cursor光標位置插入元素。

那么就有兩種情況了,正向遍歷的時候,就是在當前元素下一個索引位置插入,而反向遍歷時,就是在當前元素索引位置插入。

  1. lastRet = -1 設置當前元素索引位置無效。
  2. cursor = i + 1 將光標位置加1

這里就有問題了,它只能保證正向遍歷的時候,不會遍歷到剛剛插入的元素。但是反向遍歷的時候,因為將cursor光標位置加一,那么下次獲取前一個元素正好是剛剛添加的元素。

  1. 因為修改了集合,那么重新賦值expectedModCount = modCount

小結

我們看到迭代器一般作為集合的內部類,調用集合中的方法,來操作集合中的元素的。它還有個重要方法checkForComodification,來判斷在迭代過程中,是否有其他線程修改了原集合。

六. SubList 子集合類

它是List集合的一部分子集,通過List集合的subList(int fromIndex, int toIndex)方法獲取(包括fromIndex但是不包括toIndex),對它的操作其實都是調用父集合對應方法,本質上子集合就是父集合一段區間的投影,而并不是獨立的。

6.1 成員屬性

    // 父集合的引用
    private final AbstractList<E> l;
    // 偏移量
    private final int offset;
    // 子集合的大小
    private int size;

6.2 構造函數

     SubList(AbstractList<E> list, int fromIndex, int toIndex) {
        if (fromIndex < 0)
            throw new IndexOutOfBoundsException("fromIndex = " + fromIndex);
        if (toIndex > list.size())
            throw new IndexOutOfBoundsException("toIndex = " + toIndex);
        if (fromIndex > toIndex)
            throw new IllegalArgumentException("fromIndex(" + fromIndex +
                                               ") > toIndex(" + toIndex + ")");
        l = list;
        offset = fromIndex;
        // 所以子集合不包含toIndex索引位置的元素,不然這里要加1
        size = toIndex - fromIndex;
        this.modCount = l.modCount;
    }

對成員屬性進行初始化賦值。注意子集合SubList的modCount和父集合的modCount值一樣。

6.3 檢測的方法

    // 檢查父集合有沒有被修改
    private void checkForComodification() {
        if (this.modCount != l.modCount)
            throw new ConcurrentModificationException();
    }
// 獲取元素的時候,要檢查索引位置,要在[0, size)區間內
    private void rangeCheck(int index) {
        if (index < 0 || index >= size)
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    }

    // 添加元素的時候,要檢查索引位置,要在[0, size]區間內,包括size位置,表示在集合末尾添加元素
    private void rangeCheckForAdd(int index) {
        if (index < 0 || index > size)
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    }

6.4 操作集合

子集合操作元素的方法都是調用父集合對應方法,因為它本身就是父集合的一個投影。下面以add(int index, E element)方法為例

   public void add(int index, E element) {
        rangeCheckForAdd(index);
        checkForComodification();
        l.add(index+offset, element);
        this.modCount = l.modCount;
        size++;
    }
  1. rangeCheckForAdd(index) 檢查索引是否越界
  2. checkForComodification() 檢查父集合有沒有沒被修改
  3. l.add(index+offset, element) 調用父集合對應方法,添加元素,注意索引要加上偏移量,就是在父集合對應索引的值。
  4. this.modCount = l.modCount 更改子集合modCount的值。
  5. 因為添加了一個元素,所以size要自增。

6.5 迭代器

因為子集合是父集合區間的投影,那么采用父集合的迭代器就很方便實現子集合的迭代器了。

    public ListIterator<E> listIterator(final int index) {
        // 檢查父集合有沒有被修改
        checkForComodification();
        // 檢查索引是否越界
        rangeCheckForAdd(index);

        return new ListIterator<E>() {
            // 得到父集合的迭代器,將開始位置設在index+offset
            private final ListIterator<E> i = l.listIterator(index+offset);

            // nextIndex方法返回是子集合索引位置。 使用i.nextIndex() - offset得到
            public boolean hasNext() {
                return nextIndex() < size;
            }

            // 調用父集合迭代器對應方法
            public E next() {
                if (hasNext())
                    return i.next();
                else
                    throw new NoSuchElementException();
            }
            
            public boolean hasPrevious() {
                return previousIndex() >= 0;
            }
            // 調用父集合迭代器對應方法
            public E previous() {
                if (hasPrevious())
                    return i.previous();
                else
                    throw new NoSuchElementException();
            }

            // 減去偏移量,得到在子集合真實索引位置
            public int nextIndex() {
                return i.nextIndex() - offset;
            }
            // 減去偏移量,得到在子集合真實索引位置
            public int previousIndex() {
                return i.previousIndex() - offset;
            }

            // 調用父集合迭代器對應方法,并重設modCount和size的值
            public void remove() {
                i.remove();
                SubList.this.modCount = l.modCount;
                size--;
            }

            // 調用父集合迭代器對應方法
            public void set(E e) {
                i.set(e);
            }

            // 調用父集合迭代器對應方法,并重設modCount和size的值
            public void add(E e) {
                i.add(e);
                SubList.this.modCount = l.modCount;
                size++;
            }
        };
    }

可以看出來,都是調用父集合迭代器對應方法,但是要注意一下,子集合在父集合的偏移量。

七. RandomAccessSubList 子集合類

它繼承SubList類,并實現RandomAccess這個可隨機訪問的標記接口。

class RandomAccessSubList<E> extends SubList<E> implements RandomAccess {
    RandomAccessSubList(AbstractList<E> list, int fromIndex, int toIndex) {
        super(list, fromIndex, toIndex);
    }

    public List<E> subList(int fromIndex, int toIndex) {
        return new RandomAccessSubList<>(this, fromIndex, toIndex);
    }
}

總結

AbstractList抽樣類實現了List接口的一些工具性的方法,它內部還實現了迭代器內部類。繼承AbstractList抽樣類值需要復寫get(int index)和size()方法。

List 接口

List接口繼承自Collection接口,它提供了一種索引概念,我們可以通過索引來查找,修改,或刪除元素。它新添加的方法:

  1. void add(int index, E element); 在指定索引位置刪除元素
  2. boolean addAll(int index, Collection<? extends E> c); 在指定索引位置 添加一個集合中全部元素
  3. E remove(int index); 移除指定索引位置元素
  4. E set(int index, E element); 替換指定索引位置的元素
  5. E get(int index); 獲取指定索引處元素
  6. int indexOf(Object o); 查找o對象在集合中的索引位置(正向查找)
  7. int lastIndexOf(Object o); 查找o對象在集合中的索引位置(反向查找)
  8. List<E> subList(int fromIndex, int toIndex); 返回fromIndex位置到toIndex位置的子集合,(包括fromIndex,不包括toIndex)
  9. ListIterator<E> listIterator(); 返回功能性更強的迭代器ListIterator
  10. ListIterator<E> listIterator(int index);

ListIterator接口

ListIterator接口繼承自Iterator接口,功能更加強大

  1. boolean hasPrevious(); 和 E previous(); 反向遍歷集合使用
  2. int nextIndex(); 和int previousIndex(); 返回索引位置
  3. void set(E e); 用元素e替換當前索引位置的元素
  4. void add(E e); 向集合中添加元素

AbstractList 抽樣類

  1. 需要子類復寫的兩個方法get(int index)和size() 方法。
  2. void add(int index, E element)、E remove(int index) 與E set(int index, E element)都是直接拋出異常。也就是說子類不復寫這三個方法,是不能修改集合元素的。
  3. 它有一個非常重要的成員屬性modCount。它的作用是在多線程環境中,判斷某個正在遍歷的集合,是否被別的線程修改了。如果是,那么就會拋出ConcurrentModificationException異常。
  4. 它有兩個內部迭代器實現類Itr和ListItr

內部類Itr和ListItr

  1. 使用cursor成員屬性 表示索引位置
  2. 使用lastRet成員屬性 表示迭代器遍歷到的元素索引位置
  3. 使用 expectedModCount 來判斷原集合是否被修改,拋出ConcurrentModificationException異常。
  4. 調用List的get(int index)方法獲取集合元素
  5. 調用List的set(int index, E element)方法替換元素
  6. 調用List的remove(int index)方法刪除元素

例子

    public static void main(String[] args) {
        // 創建AbstractCollection實例,需要復寫iterator和size,這是一個不可修改集合
        List<Integer> list = new AbstractList<Integer>() {
            int[] data = {1,2,3,4,5}; // 集合中的元素
            @Override
            public int size() {
                return data.length;
            }

            @Override
            public Integer get(int index) {
                return data[index];
            }
        };

        // 遍歷集合
        for (int i : list) {
            System.out.println(i);
        }
        // 測試contains方法
        System.out.println(list.contains(3));
        System.out.println(list.contains(10));

        List<Integer> other = new ArrayList<>();
        other.add(2);
        other.add(3);
        other = other.subList(0,1);
        for (int i : other) {
            System.out.println(i);
        }
        // 測試containsAll方法
        System.out.println(list.containsAll(other));

        // 查詢元素對應索引位置
        System.out.println(list.indexOf(2));
    }

qw

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,460評論 6 538
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,067評論 3 423
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 177,467評論 0 382
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,468評論 1 316
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,184評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,582評論 1 325
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,616評論 3 444
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,794評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,343評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,096評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,291評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,863評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,513評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,941評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,190評論 1 291
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,026評論 3 396
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,253評論 2 375

推薦閱讀更多精彩內容