JDK8新特性之Stream

前言

我们前面说了 lambda表达式,这次我们就将下JDK8的另一个新特性,流(Stream)

stream和lambda搭配使用效果更佳,(如果你没有学过lambda表达式,最好先学习下lambda表达式)

看着逼格更高,也更简洁

我们就拿之前的lambda表达式的举例

我们需要找出集合中所有的 男同学 按照年龄从小到大排序 并且打印出来,我们就这样写

studentList.stream()
                .filter(student -> "男".equals(student.getSex()))
                .sorted((x, y) -> x.getAge()-y.getAge())
                .forEach(student -> System.out.println(JSON.toJSONString(student, true)));

项目代码在 github上 。jdk8 stream流的演示,有一个lambda_demo,找到测试的文件夹就行
(如果文章图片失效也可以在上面看,文章更全更新更及时)

image

定义

Stream(流)是一个来自数据源元素队列并支持聚合操作

  • 元素是特定类型的对象,形成一个队列。 Java中的Stream并不会存储元素,而是按需计算。
  • 数据源 流的来源。 可以是集合,数组,I/O channel, 产生器generator 等。
  • 聚合操作 类似SQL语句一样的操作, 比如filter, map, reduce, find, match, sorted等。

流的处理流程一般是这样

+--------------------+       +------+   +------+   +---+   +-------+
| stream of elements +-----> |filter+-> |sorted+-> |map+-> |collect|
+--------------------+       +------+   +------+   +---+   +-------+

聚合操作按照分法可以分成两种:中间操作,终端操作

  • 中间操作
    • 无状态操作
      • filter、map、peek等
    • 有状态操作
      • Sorted、distinct、limit等
  • 终端操作
    • 非短路操作
      • foreach、count、collect等
    • 短路操作
      • findFirst、findAny、anyMatch等

中间操作

中间操作和很多命令像不像我们 sql 里面的命令,你可以理解为我们的那些限制语句,通过这些手段得到我们想要的一些数据

终端操作

顾名思义,就是指最后的操作。一个流里面进行完终端操作之后就不能再进行其他操作了

无状态操作

就是不需要全部遍历完之后才能得到,比如 我上面的代码,我只看这个元素符不符合,不符合我就不要,不需要遍历完全部元素。与此相对,有状态操作就是需要整个集合遍历完才行,比如我们的 sorted,我不遍历完所有元素,我怎么知道哪一个最大,哪一个最小

短路操作

就是找到一个我们就不往下执行了。与此相反,非短路操作也很好理解

各个方法演示

我的集合中有如下元素

private static List<Student> studentList = new ArrayList<Student>() {
        {
            add(new Student("张三丰", 20, "男", "体育",
                    180, 75, "太上老君"));
            add(new Student("张无忌", 18, "男", "语文",
                    178, 73, "文曲星"));
            add(new Student("赵敏", 17, "女", "数学",
                    170, 50, "太白金星"));
            add(new Student("金毛狮王", 25, "男", "体育",
                    176, 80, "太白金星"));
            add(new Student("周芷若", 16, "女", "语文",
                    168, 48, "太上老君"));
            add(new Student("张三", 21, "男", "英语",
                    172, 65, "如来"));
            add(new Student("赵勇", 26, "男", "体育",
                    188, 80, "太上老君"));


        }
    };

中间操作

无状态操作

filter

filter,就是过滤掉那些不符合你设定的条件的元素

我们看源码

/**
     * Returns a stream consisting of the elements of this stream that match
     * the given predicate.
     *
     * <p>This is an <a href="package-summary.html#StreamOps">intermediate
     * operation</a>.
     *
     * @param predicate a <a href="package-summary.html#NonInterference">non-interfering</a>,
     *                  <a href="package-summary.html#Statelessness">stateless</a>
     *                  predicate to apply to each element to determine if it
     *                  should be included
     * @return the new stream
     */
    Stream<T> filter(Predicate<? super T> predicate);

// 再看他的参数,记不记得我当初 讲 lambda 时候讲到的 这个
// Predicate 接口 是输入一个类型,返回一个bool值

@FunctionalInterface
public interface Predicate<T> {

    /**
     * Evaluates this predicate on the given argument.
     *
     * @param t the input argument
     * @return {@code true} if the input argument matches the predicate,
     * otherwise {@code false}
     */
    boolean test(T t);
}

所以我们使用的时候,返回的是一个bool值,如下所示

Equals 方法返回的是一个bool值

studentList.stream()
                .filter(student -> "男".equals(student.getSex()))
                .forEach(student -> System.out.println(JSON.toJSONString(student, true)));
    

结果,我们看到它里面已经过滤掉了女同学

{
    "age":20,
    "height":180,
    "name":"张三丰",
    "sex":"男",
    "subject":"体育",
    "teacher":"太上老君",
    "weight":75
}
{
    "age":18,
    "height":178,
    "name":"张无忌",
    "sex":"男",
    "subject":"语文",
    "teacher":"文曲星",
    "weight":73
}
{
    "age":25,
    "height":176,
    "name":"金毛狮王",
    "sex":"男",
    "subject":"体育",
    "teacher":"太白金星",
    "weight":80
}
{
    "age":21,
    "height":172,
    "name":"张三",
    "sex":"男",
    "subject":"英语",
    "teacher":"如来",
    "weight":65
}
{
    "age":26,
    "height":188,
    "name":"赵勇",
    "sex":"男",
    "subject":"体育",
    "teacher":"太上老君",
    "weight":80
}


map

map的作用是,将一个类型的集合转化为另一个类型的集合

我们来看他的源码

    /**
     * Returns a stream consisting of the results of applying the given
     * function to the elements of this stream.
     *
     * <p>This is an <a href="package-summary.html#StreamOps">intermediate
     * operation</a>.
     * @return the new stream
     */
    <R> Stream<R> map(Function<? super T, ? extends R> mapper);


        // 他要传入的是一个 Function 接口,作用是输入一个类型,返回另一个类型
    @FunctionalInterface
    public interface Function<T, R> {

        /**
         * Applies this function to the given argument.
         *
         * @param t the function argument
         * @return the function result
         */
        R apply(T t);
    }

我们用它生成一个 学生选课的集合。我们输入的是一个Student类型的集合,返回的是一个 String类型的集合

    @Test
    public void mapTest(){

        studentList.stream()
                .map(student -> student.getSubject())
                .forEach(student -> System.out.println(JSON.toJSONString(student, true)));
    }

结果如下

"体育"
"语文"
"数学"
"体育"
"语文"
"英语"
"体育"

faltMap

将一个类型的集合转换成另一个类型的流(注意和map区分)

/**
     * Returns a stream consisting of the results of replacing each element of
     * this stream with the contents of a mapped stream produced by applying
     * the provided mapping function to each element.  Each mapped stream is
     * {@link java.util.stream.BaseStream#close() closed} after its contents
     * have been placed into this stream.  (If a mapped stream is {@code null}
     * an empty stream is used, instead.)
     *
     * @return the new stream
     */
    <R> Stream<R> flatMap(Function<? super T, ? extends Stream<? extends R>> mapper);


        // 他也是 Function接口,但是另一个参数是继承自 Stream类的
    /**
     * 将一个类型的集合抓换成流.我们把一个字符串流转换成一个字符流
     */
    @Test
    public void flatMapTest() {

        studentList.stream()
                .flatMap(student -> Arrays.stream(student.getName().split("")))
                .forEach(stu -> System.out.println(JSON.toJSONString(stu,true)));
    }

peek

peek和foreach很相似,区别是 ,一个是中间操作,一个是终端操作。peek用完之后还能被其他操作进行处理。

/**
     * Returns a stream consisting of the elements of this stream, additionally
     * performing the provided action on each element as elements are consumed
     * from the resulting stream.
     *
    
     * @return the new stream
     */
    Stream<T> peek(Consumer<? super T> action);


// 我们看到他的函数接口是Consumer,他是输入一个参数,但是不会有返回值
@FunctionalInterface
public interface Consumer<T> {

    /**
     * Performs this operation on the given argument.
     *
     * @param t the input argument
     */
    void accept(T t);
}
    /**
     * peek 方法
     */
    @Test
    public void peekTest() {
        studentList.stream()
                .peek(student -> System.out.println(student.getName()))
                .forEach(stu -> System.out.println(JSON.toJSONString(stu,true)));
    }
张三丰
{
    "age":20,
    "height":180,
    "name":"张三丰",
    "sex":"男",
    "subject":"体育",
    "teacher":"太上老君",
    "weight":75
}
张无忌
{
    "age":18,
    "height":178,
    "name":"张无忌",
    "sex":"男",
    "subject":"语文",
    "teacher":"文曲星",
    "weight":73
}
赵敏
{
    "age":17,
    "height":170,
    "name":"赵敏",
    "sex":"女",
    "subject":"数学",
    "teacher":"太白金星",
    "weight":50
}
金毛狮王
{
    "age":25,
    "height":176,
    "name":"金毛狮王",
    "sex":"男",
    "subject":"体育",
    "teacher":"太白金星",
    "weight":80
}
周芷若
{
    "age":16,
    "height":168,
    "name":"周芷若",
    "sex":"女",
    "subject":"语文",
    "teacher":"太上老君",
    "weight":48
}
张三
{
    "age":21,
    "height":172,
    "name":"张三",
    "sex":"男",
    "subject":"英语",
    "teacher":"如来",
    "weight":65
}
赵勇
{
    "age":26,
    "height":188,
    "name":"赵勇",
    "sex":"男",
    "subject":"体育",
    "teacher":"太上老君",
    "weight":80
}

我们看到 人名和student类是交替打印的,也就是执行了一次 peek,执行了一次 foreach,那么每次都是这样吗?不是的,我们来看下一节

有状态操作

sorted

sorted是用来排序的

 /**
     * Returns a stream consisting of the elements of this stream, sorted
     * according to the provided {@code Comparator}
     * @return the new stream
     */
    Stream<T> sorted(Comparator<? super T> comparator);

        // 他提供了一个 Comparator接口
        @FunctionalInterface
        public interface Comparator<T> {
    /**
     * Compares its two arguments for order.  Returns a negative integer,
     * zero, or a positive integer as the first argument is less than, equal
     * to, or greater than the second.<p>
     */
    int compare(T o1, T o2);
/**
     * sorted 方法,按照年龄大小排序
     */
    @Test
    public void sortedTest() {
        studentList.stream()
                .peek(student -> System.out.println(student.getName()))
                .sorted((x,y) -> x.getAge()-y.getAge())
                .forEach(stu -> System.out.println(JSON.toJSONString(stu,true)));
    }
张三丰
张无忌
赵敏
金毛狮王
周芷若
张三
赵勇
{
    "age":16,
    "height":168,
    "name":"周芷若",
    "sex":"女",
    "subject":"语文",
    "teacher":"太上老君",
    "weight":48
}
{
    "age":17,
    "height":170,
    "name":"赵敏",
    "sex":"女",
    "subject":"数学",
    "teacher":"太白金星",
    "weight":50
}
{
    "age":18,
    "height":178,
    "name":"张无忌",
    "sex":"男",
    "subject":"语文",
    "teacher":"文曲星",
    "weight":73
}
{
    "age":20,
    "height":180,
    "name":"张三丰",
    "sex":"男",
    "subject":"体育",
    "teacher":"太上老君",
    "weight":75
}
{
    "age":21,
    "height":172,
    "name":"张三",
    "sex":"男",
    "subject":"英语",
    "teacher":"如来",
    "weight":65
}
{
    "age":25,
    "height":176,
    "name":"金毛狮王",
    "sex":"男",
    "subject":"体育",
    "teacher":"太白金星",
    "weight":80
}
{
    "age":26,
    "height":188,
    "name":"赵勇",
    "sex":"男",
    "subject":"体育",
    "teacher":"太上老君",
    "weight":80
}

我们看到,这次是先打印peek的内容,再打印 foreach的内容,为什么会这样呢?

因为之前我们除只有无状态的方法,他不需要遍历完其他全部的操作,所以他就交替打印了,也符合流的特性。但是我们这一次有了有状态的操作,就只能等到处理完全部元素之后能进行foreach的遍历操作

distinct

去重

/**
     * Returns a stream consisting of the distinct elements (according to
     * {@link Object#equals(Object)}) of this stream
     *
     * @return the new stream
     */
    Stream<T> distinct();
/**
 * distinct 方法,找出所有老师
 */
@Test
public void distinctTest() {
    studentList.stream()
            .map(student -> student.getTeacher())
            .distinct()
            .forEach(stu -> System.out.println(JSON.toJSONString(stu,true)));
}
"太上老君"
"文曲星"
"太白金星"
"如来"

limit

    /**
     * Returns a stream consisting of the elements of this stream, truncated
     * to be no longer than {@code maxSize} in length.
     *
     * @param maxSize the number of elements the stream should be limited to
     * @return the new stream
     * @throws IllegalArgumentException if {@code maxSize} is negative
     */
    Stream<T> limit(long maxSize);   


   /**
     * limit 方法,只显示前4个
     */
    @Test
    public void limitTest() {
        studentList.stream()
                .limit(4)
                .forEach(stu -> System.out.println(JSON.toJSONString(stu,true)));
    }
{
    "age":20,
    "height":180,
    "name":"张三丰",
    "sex":"男",
    "subject":"体育",
    "teacher":"太上老君",
    "weight":75
}
{
    "age":18,
    "height":178,
    "name":"张无忌",
    "sex":"男",
    "subject":"语文",
    "teacher":"文曲星",
    "weight":73
}
{
    "age":17,
    "height":170,
    "name":"赵敏",
    "sex":"女",
    "subject":"数学",
    "teacher":"太白金星",
    "weight":50
}
{
    "age":25,
    "height":176,
    "name":"金毛狮王",
    "sex":"男",
    "subject":"体育",
    "teacher":"太白金星",
    "weight":80
}

skip

/**
     * Returns a stream consisting of the remaining elements of this stream
     * after discarding the first {@code n} elements of the stream.
     * If this stream contains fewer than {@code n} elements then an
     * empty stream will be returned.
     
     * @throws IllegalArgumentException if {@code n} is negative
     */
    Stream<T> skip(long n);
    


/**
     * skip 方法,跳过前4个
     */
    @Test
    public void skipTest() {
        studentList.stream()
                .skip(4)
                .forEach(stu -> System.out.println(JSON.toJSONString(stu,true)));
    }
{
    "age":16,
    "height":168,
    "name":"周芷若",
    "sex":"女",
    "subject":"语文",
    "teacher":"太上老君",
    "weight":48
}
{
    "age":21,
    "height":172,
    "name":"张三",
    "sex":"男",
    "subject":"英语",
    "teacher":"如来",
    "weight":65
}
{
    "age":26,
    "height":188,
    "name":"赵勇",
    "sex":"男",
    "subject":"体育",
    "teacher":"太上老君",
    "weight":80
}

skip+limit可以完成分页功能

    /**
     * limit+skip 方法,完成分页功能
     */
    @Test
    public void skipAndLimitTest() {
        studentList.stream()
                .skip(1 * 4)
                .limit(4)
                .forEach(stu -> System.out.println(JSON.toJSONString(stu, true)));
    }

终端操作

短路

anyMatch

    /**
     * Returns whether any elements of this stream match the provided
     * predicate.  May not evaluate the predicate on all elements if not
     * necessary for determining the result.  If the stream is empty then
     * {@code false} is returned and the predicate is not evaluated.
     
     * @return {@code true} if any elements of the stream match the provided
     * predicate, otherwise {@code false}
     */
    boolean anyMatch(Predicate<? super T> predicate);



/**
     * anyMatch方法,判断是否有一个满足条件
     */
    @Test
    public void anyMatchTest() {
        final boolean b = studentList.stream()
                .peek(student -> System.out.println(student))
                .allMatch(student -> student.getAge() > 100);
        System.out.println(b);
    }

为啥说是短路操作。我们测试一下就知道

我们看到,它只打印了一个元素。因为第一个就不满足,他就不会再往下执行了,直接返回false

Student(name=张三丰, age=20, sex=男, subject=体育, height=180, weight=75, teacher=太上老君)
false

allMatch

 /**
     * Returns whether all elements of this stream match the provided predicate.
     * May not evaluate the predicate on all elements if not necessary for
     * determining the result.  If the stream is empty then {@code true} is
     * returned and the predicate is not evaluated
     * @return {@code true} if either all elements of the stream match the
     * provided predicate or the stream is empty, otherwise {@code false}
     */

        // 我们看到他的接口是一个 Predicate ,这个我们之前介绍过,返回的是一个bool值
    boolean allMatch(Predicate<? super T> predicate);




    /**
     * allMatch方法,判断是否全部满足输入的条件
     */
    @Test
    public void allMatchTest() {
        final boolean b = studentList.stream()
                .allMatch(student -> student.getAge() < 100);
        System.out.println(b);
    }

findFirst

/**
     * Returns an {@link Optional} describing the first element of this stream,
     * or an empty {@code Optional} if the stream is empty.  If the stream has
     * no encounter order, then any element may be returned.
     
     * @return an {@code Optional} describing the first element of this stream,
     * or an empty {@code Optional} if the stream is empty
     * @throws NullPointerException if the element selected is null
     */

        // 他返回的是一个 Optional对象
    Optional<T> findFirst();   


/**
     * findFirst方法
     */
    @Test
    public void findFirstTest() {
        final Optional<Student> first = studentList.stream()
                .peek(student -> System.out.println(student))
                .findFirst();
        System.out.println(first.get());
    }

findAny

/**
     * Returns an {@link Optional} describing some element of the stream, or an
     * empty {@code Optional} if the stream is empty.
     *
     * <p>This is a <a href="package-summary.html#StreamOps">short-circuiting
     * terminal operation</a>.
     *
     * <p>The behavior of this operation is explicitly nondeterministic; it is
     * free to select any element in the stream.  This is to allow for maximal
     * performance in parallel operations; the cost is that multiple invocations
     * on the same source may not return the same result.  (If a stable result
     * is desired, use {@link #findFirst()} instead.)
     *
     * @return an {@code Optional} describing some element of this stream, or an
     * empty {@code Optional} if the stream is empty
     * @throws NullPointerException if the element selected is null
     * @see #findFirst()
     */

        // 注意 他的解释中说了,findAny可以发挥并行操作的性能,但是如果你在并行的时候想要一个稳定的结果,要用 findFirst。
    Optional<T> findAny();
    

    /**
        * 因为我们使用的是串行的操作,所以并不影响结果,和findFirst 的结果一样
        */
        @Test
    public void findAnyTest() {
        final Optional<Student> first = studentList.stream()
                .peek(student -> System.out.println(student))
                .findAny();
        System.out.println(first.get());
    }

max

/**
     * Returns the maximum element of this stream according to the provided
     * {@code Comparator}.  This is a special case of a
     * <a href="package-summary.html#Reduction">reduction</a>.
     *
     * <p>This is a <a href="package-summary.html#StreamOps">terminal
     * operation</a>.
     *
     * @param comparator a <a href="package-summary.html#NonInterference">non-interfering</a>,
     *                   <a href="package-summary.html#Statelessness">stateless</a>
     *                   {@code Comparator} to compare elements of this stream
     * @return an {@code Optional} describing the maximum element of this stream,
     * or an empty {@code Optional} if the stream is empty
     * @throws NullPointerException if the maximum element is null
     */
        // 返回的是一个 Optional类
    Optional<T> max(Comparator<? super T> comparator);


  /**
     * max 方法测试,输出最大的年龄。如果从这里点进入就是使用的 intStream接口,和之前的还不一样
     */
    @Test
    public void maxTest() {
        final OptionalInt max = studentList.stream()
                .mapToInt(stu -> stu.getAge())
                .max();
        System.out.println(max.getAsInt());
    }


终端非短路

collect

    

        @Test
    public void collectTest(){
        final List<Student> list = studentList.stream()
                .filter(student -> "女".equals(student.getSex()))
                .collect(Collectors.toList());

        System.out.println(JSON.toJSONString(list,true));
    }
[
    {
        "age":17,
        "height":170,
        "name":"赵敏",
        "sex":"女",
        "subject":"数学",
        "teacher":"太白金星",
        "weight":50
    },
    {
        "age":16,
        "height":168,
        "name":"周芷若",
        "sex":"女",
        "subject":"语文",
        "teacher":"太上老君",
        "weight":48
    }
]

groupBy 进行分类

/**
     * Returns a {@code Collector} implementing a "group by" operation on
     * input elements of type {@code T}, grouping elements according to a
     * classification function, and returning the results in a {@code Map}.
     *
     * <p>The classification function maps elements to some key type {@code K}.
     * The collector produces a {@code Map<K, List<T>>} whose keys are the
     * values resulting from applying the classification function to the input
     * elements, and whose corresponding values are {@code List}s containing the
     * input elements which map to the associated key under the classification
     * function.
     */

        // 可以看到,他的参数也是一个 Function接口,
    public static <T, K> Collector<T, ?, Map<K, List<T>>>
    groupingBy(Function<? super T, ? extends K> classifier) {
        return groupingBy(classifier, toList());
    }



    @Test
    public void collectTest(){
//        final List<Student> list = studentList.stream()
//                .filter(student -> "女".equals(student.getSex()))
//                .collect(Collectors.toList());

      // 按照性别分类
        final Map<String, List<Student>> list = studentList.stream()
                .collect(Collectors.groupingBy(s -> s.getSex()));

        System.out.println(JSON.toJSONString(list,true));
    }


{
    "女":[
        {
            "age":17,
            "height":170,
            "name":"赵敏",
            "sex":"女",
            "subject":"数学",
            "teacher":"太白金星",
            "weight":50
        },
        {
            "age":16,
            "height":168,
            "name":"周芷若",
            "sex":"女",
            "subject":"语文",
            "teacher":"太上老君",
            "weight":48
        }
    ],
    "男":[
        {
            "age":20,
            "height":180,
            "name":"张三丰",
            "sex":"男",
            "subject":"体育",
            "teacher":"太上老君",
            "weight":75
        },
        {
            "age":18,
            "height":178,
            "name":"张无忌",
            "sex":"男",
            "subject":"语文",
            "teacher":"文曲星",
            "weight":73
        },
        {
            "age":25,
            "height":176,
            "name":"金毛狮王",
            "sex":"男",
            "subject":"体育",
            "teacher":"太白金星",
            "weight":80
        },
        {
            "age":21,
            "height":172,
            "name":"张三",
            "sex":"男",
            "subject":"英语",
            "teacher":"如来",
            "weight":65
        },
        {
            "age":26,
            "height":188,
            "name":"赵勇",
            "sex":"男",
            "subject":"体育",
            "teacher":"太上老君",
            "weight":80
        }
    ]
}

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 219,701评论 6 508
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,649评论 3 396
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 166,037评论 0 356
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,994评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,018评论 6 395
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,796评论 1 308
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,481评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,370评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,868评论 1 319
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,014评论 3 338
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,153评论 1 352
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,832评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,494评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,039评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,156评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,437评论 3 373
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,131评论 2 356

推荐阅读更多精彩内容