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阅读 159,015评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,262评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,727评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,986评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,363评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,610评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,871评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,582评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,297评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,551评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,053评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,385评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,035评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,079评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,841评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,648评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,550评论 2 270

推荐阅读更多精彩内容