`

Java8 流处理

    博客分类:
  • java
阅读更多

JDK8新特性Optional类的时候,提到对于Optional的两个操作映射和过滤设计到JDK提供的流式出来。这篇文章便详细的介绍流式处理:

. 流式处理简介

流式处理给开发者的第一感觉就是让集合操作变得简洁了许多,通常我们需要多行代码才能完成的操作,借助于流式处理可以在一行中实现。比如我们希望对一个包含整数的集合中筛选出所有的偶数,并将其封装成为一个新的List返回,那么在java8之前,我们需要通过如下代码实现:

对于一个nums的集合:

List<Integer> evens = new ArrayList<>();

for (final Integer num : nums) {

    if (num % 2 == 0) {

        evens.add(num);

    }

}

 

通过java8的流式处理,我们可以将代码简化为:

List<Integer> evens = nums.stream().filter(num -> num % 2 == 0).collect(Collectors.toList());

 

先简单解释一下上面这行语句的含义,stream()操作将集合转换成一个流,filter()执行我们自定义的筛选处理,这里是通过lambda表达式筛选出所有偶数,最后我们通过collect()对结果进行封装处理,并通过Collectors.toList()指定其封装成为一个List集合返回。

由上面的例子可以看出,java8的流式处理极大的简化了对于集合的操作,实际上不光是集合,包括数组、文件等,只要是可以转换成流,我们都可以借助流式处理,类似于我们写SQL语句一样对其进行操作。java8通过内部迭代来实现对流的处理,一个流式处理可以分为三个部分:转换成流、中间操作、终端操作。如下图:

20161028151405428.png

以集合为例,一个流式处理的操作我们首先需要调用stream()函数将其转换成流,然后再调用相应的中间操作达到我们需要对集合进行的操作,比如筛选、转换等,最后通过终端操作对前面的结果进行封装,返回我们需要的形式。

 

 

. 中间操作

我们定义一个简单的学生实体类,用于后面的例子演示:

public class Student {

 

    /** 学号 */

    private long id;

 

    private String name;

 

    private int age;

 

    /** 年级 */

    private int grade;

 

    /** 专业 */

    private String major;

 

    /** 学校 */

    private String school;

 

    // 省略gettersetter

}

 

初始化:

// 初始化

List<Student> students = new ArrayList<Student>() {

    {

        add(new Student(20160001, "孔明", 20, 1, "土木工程", "武汉大学"));

        add(new Student(20160002, "伯约", 21, 2, "信息安全", "武汉大学"));

        add(new Student(20160003, "玄德", 22, 3, "经济管理", "武汉大学"));

        add(new Student(20160004, "云长", 21, 2, "信息安全", "武汉大学"));

        add(new Student(20161001, "翼德", 21, 2, "机械与自动化", "华中科技大学"));

        add(new Student(20161002, "元直", 23, 4, "土木工程", "华中科技大学"));

        add(new Student(20161003, "奉孝", 23, 4, "计算机科学", "华中科技大学"));

        add(new Student(20162001, "仲谋", 22, 3, "土木工程", "浙江大学"));

        add(new Student(20162002, "鲁肃", 23, 4, "计算机科学", "浙江大学"));

        add(new Student(20163001, "丁奉", 24, 5, "土木工程", "南京大学"));

    }

};

过滤:

过滤,顾名思义就是按照给定的要求对集合进行筛选满足条件的元素,java8提供的筛选操作包括:filterdistinctlimitskip

filter 
在前面的例子中我们已经演示了如何使用filter,其定义为:Stream<T> filter(Predicate<? super T> predicate),filter接受一个谓词Predicate,我们可以通过这个谓词定义筛选条件,在介绍lambda表达式时我们介绍过Predicate是一个函数式接口,其包含一个test(T t)方法,该方法返回boolean。现在我们希望从集合students中筛选出所有武汉大学的学生,那么我们可以通过filter来实现,并将筛选操作作为参数传递给filter

List<Student> whuStudents = students.stream()

                                    .filter(student -> "武汉大学".equals(student.getSchool()))

                                    .collect(Collectors.toList());

 

distinct

distinct操作类似于我们在写SQL语句时,添加的DISTINCT关键字,用于去重处理,distinct基于Object.equals(Object)实现,回到最开始的例子,假设我们希望筛选出所有不重复的偶数,那么可以添加distinct操作:

List<Integer> evens = nums.stream()

                        .filter(num -> num % 2 == 0).distinct()

                        .collect(Collectors.toList());

 

limit

limit操作也类似于SQL语句中的LIMIT关键字,不过相对功能较弱,limit返回包含前n个元素的流,当集合大小小于n时,则返回实际长度,比如下面的例子返回前两个专业为土木工程专业的学生:

List<Student> civilStudents = students.stream()

                                    .filter(student -> "土木工程".equals(student.getMajor())).limit(2)

                                    .collect(Collectors.toList());

说到limit,不得不提及一下另外一个流操作:sorted。该操作用于对流中元素进行排序,sorted要求待比较的元素必须实现Comparable接口,如果没有实现也不要紧,我们可以将比较器作为参数传递给sorted(Comparator

List<Student> sortedCivilStudents = students.stream()

                                            .filter(student -> "土木工程".equals(student.getMajor())).sorted((s1, s2) -> s1.getAge() - s2.getAge())

                                            .limit(2)

                                            .collect(Collectors.toList());

 

skip

skip操作与limit操作相反,如同其字面意思一样,是跳过前n个元素,比如我们希望找出排序在2之后的土木工程专业的学生,那么可以实现为:

List<Student> civilStudents = students.stream()

                                    .filter(student -> "土木工程".equals(student.getMajor()))

                                    .skip(2)

                                    .collect(Collectors.toList());

通过skip,就会跳过前面两个元素,返回由后面所有元素构造的流,如果n大于满足条件的集合的长度,则会返回一个空的集合。

映射处理:

SQL中,借助SELECT关键字后面添加需要的字段名称,可以仅输出我们需要的字段数据,而流式处理的映射操作也是实现这一目的,在java8的流式处理中,主要包含两类映射操作:mapflatMap

map

举例说明,假设我们希望筛选出所有专业为计算机科学的学生姓名,那么我们可以在filter筛选的基础之上,通过map将学生实体映射成为学生姓名字符串,具体实现如下:

List<String> names = students.stream()

                            .filter(student -> "计算机科学".equals(student.getMajor()))

                            .map(Student::getName).collect(Collectors.toList());

熟悉Optional的话可以看出,这与Optional的处理方法一样。

除了上面这类基础的mapjava8还提供了mapToDouble(ToDoubleFunction<? super T> mapper),mapToInt(ToIntFunction<? super T> mapper),mapToLong(ToLongFunction<? super T> mapper),这些映射分别返回对应类型的流,java8为这些流设定了一些特殊的操作,比如我们希望计算所有专业为计算机科学学生的年龄之和,那么我们可以实现如下:

int totalAge = students.stream()

                    .filter(student -> "计算机科学".equals(student.getMajor()))

                    .mapToInt(Student::getAge).sum();

通过将Student按照年龄直接映射为IntStream,我们可以直接调用提供的sum()方法来达到目的,此外使用这些数值流的好处还在于可以避免jvm装箱操作所带来的性能消耗。

flatMap

flatMapmap的区别在于* flatMap是将一个流中的每个值都转成一个个流,然后再将这些流扁平化成为一个流。*举例说明,假设我们有一个字符串数组String[] strs = {java8”,“is”,“easy”,“to”,“use};,我们希望输出构成这一数组的所有非重复字符,那么我们可能首先会想到如下实现:

List<String[]> distinctStrs = Arrays.stream(strs)

                                .map(str -> str.split(""))  // 映射成为Stream<String[]>

                                .distinct()

                                .collect(Collectors.toList());

在执行map操作以后,我们得到是一个包含多个字符串(构成一个字符串的字符数组)的流,此时执行distinct操作是基于在这些字符串数组之间的对比,所以达不到我们希望的目的,此时的输出为:

[j, a, v, a, 8]

[i, s]

[e, a, s, y]

[t, o]

[u, s, e]

distinct只有对于一个包含多个字符的流进行操作才能达到我们的目的,即对Stream<String>进行操作。此时flatMap就可以达到我们的目的:

List<String> distinctStrs = Arrays.stream(strs)

                                .map(str -> str.split(""))  // 映射成为Stream<String[]>

                                .flatMap(Arrays::stream)  // 扁平化为Stream<String>

                                .distinct()

                                .collect(Collectors.toList());

flatMap将由map映射得到的Stream<String[]>,转换成由各个字符串数组映射成的流Stream<String>,再将这些小的流扁平化成为一个由所有字符串构成的大流Steam<String>,从而能够达到我们的目的。

map类似,flatMap也提供了针对特定类型的映射操作:flatMapToDouble(Function<? super T,? extends DoubleStream> mapper),flatMapToInt(Function<? super T,? extends IntStream> mapper),flatMapToLong(Function<? super T,? extends LongStream> mapper)

 

 

. 终端操作

终端操作是流式处理的最后一步,我们可以在终端操作中实现对流查找、归约等操作。

3.1 查找

allMatch

allMatch用于检测是否全部都满足指定的参数行为,如果全部满足则返回true,例如我们希望检测是否所有的学生都已满18周岁,那么可以实现为:

boolean isAdult = students.stream().allMatch(student -> student.getAge() >= 18);

 

anyMatch

anyMatch则是检测是否存在一个或多个满足指定的参数行为,如果满足则返回true,例如我们希望检测是否有来自武汉大学的学生,那么可以实现为:

boolean hasWhu = students.stream().anyMatch(student -> "武汉大学".equals(student.getSchool()));

 

noneMathch

noneMatch用于检测是否不存在满足指定行为的元素,如果不存在则返回true,例如我们希望检测是否不存在专业为计算机科学的学生,可以实现如下:

boolean noneCs = students.stream().noneMatch(student -> "计算机科学".equals(student.getMajor()));

 

findFirst

findFirst用于返回满足条件的第一个元素,比如我们希望选出专业为土木工程的排在第一个学生,那么可以实现如下:

Optional<Student> optStu = students.stream().filter(student -> "土木工程".equals(student.getMajor())).findFirst();

findFirst不携带参数,具体的查找条件可以通过filter设置,此外我们可以发现findFirst返回的是一个Optional类型.

 

findAny

findAny相对于findFirst的区别在于,findAny不一定返回第一个,而是返回任意一个,比如我们希望返回任意一个专业为土木工程的学生,可以实现如下:

Optional<Student> optStu = students.stream().filter(student -> "土木工`".equals(student.getMajor())).findAny();

实际上对于顺序流式处理而言,findFirstfindAny返回的结果是一样的,至于为什么会这样设计,接下来我们介绍的并行流式处理,当我们启用并行流式处理的时候,查找第一个元素往往会有很多限制,如果不是特别需求,在并行流式处理中使用findAny的性能要比findFirst好。

归约

前面的例子中我们大部分都是通过collect(Collectors.toList())对数据封装返回,如我的目标不是返回一个新的集合,而是希望对经过参数化操作后的集合进行进一步的运算,那么我们可用对集合实施归约操作。java8的流式处理提供了reduce方法来达到这一目的。

前面我们通过mapToIntStream<Student>映射成为IntStream,并通过IntStreamsum方法求得所有学生的年龄之和,实际上我们通过归约操作,也可以达到这一目的,实现如下:

// 前面例子中的方法

int totalAge = students.stream()

                .filter(student -> "计算机科学".equals(student.getMajor()))

                .mapToInt(Student::getAge).sum();

// 归约操作

int totalAge = students.stream()

                .filter(student -> "计算机科学".equals(student.getMajor()))

                .map(Student::getAge)

                .reduce(0, (a, b) -> a + b);

 

// 进一步简化

int totalAge2 = students.stream()

                .filter(student -> "计算机科学".equals(student.getMajor()))

                .map(Student::getAge)

                .reduce(0, Integer::sum);

 

// 采用无初始值的重载版本,需要注意返回Optional

Optional<Integer> totalAge = students.stream()

                .filter(student -> "计算机科学".equals(student.getMajor()))

                .map(Student::getAge)

                .reduce(Integer::sum);  // 去掉初始值

 

收集

前面利用collect(Collectors.toList())是一个简单的收集操作,是对处理结果的封装,对应的还有toSettoMap,以满足我们对于结果组织的需求。这些方法均来自于java.util.stream.Collectors,我们可以称之为收集器。

收集器也提供了相应的归约操作,但是与reduce在内部实现上是有区别的,收集器更加适用于可变容器上的归约操作,这些收集器广义上均基于Collectors.reducing()实现。

求学生的总人数

long count = students.stream().collect(Collectors.counting());

 

// 进一步简化

long count = students.stream().count();

 

求年龄的最大值和最小值

// 求最大年龄

Optional<Student> olderStudent = students.stream().collect(Collectors.maxBy((s1, s2) -> s1.getAge() - s2.getAge()));

 

// 进一步简化

Optional<Student> olderStudent2 = students.stream().collect(Collectors.maxBy(Comparator.comparing(Student::getAge)));

 

// 求最小年龄

Optional<Student> olderStudent3 = students.stream().collect(Collectors.minBy(Comparator.comparing(Student::getAge)));

 

求年龄总和

int totalAge4 = students.stream().collect(Collectors.summingInt(Student::getAge));

 

字符串拼接

String names = students.stream().map(Student::getName).collect(Collectors.joining());

// 输出:孔明伯约玄德云长翼德元直奉孝仲谋鲁肃丁奉

 

String names = students.stream().map(Student::getName).collect(Collectors.joining(", "));

// 输出:孔明,伯约,玄德,云长,翼德,元直,奉孝,仲谋,鲁肃,丁奉

 

. 并行流式数据处理

流式处理中的很多都适合采用分而治之的思想,从而在处理集合较大时,极大的提高代码的性能,java8的设计者也看到了这一点,所以提供了并行流式处理。上面的例子中我们都是调用stream()方法来启动流式处理,java8还提供了parallelStream()来启动并行流式处理,parallelStream()本质上基于java7Fork-Join框架实现,其默认的线程数为宿主机的内核数。

启动并行流式处理虽然简单,只需要将stream()替换成parallelStream()即可,但既然是并行,就会涉及到多线程安全问题,所以在启用之前要先确认并行是否值得(并行的效率不一定高于顺序执行),另外就是要保证线程安全。此两项无法保证,那么并行毫无意义,毕竟结果比速度更加重要。

 

 

 

 

 

 

分享到:
评论

相关推荐

    基于Java的视频流处理(原创)

    在本项目中,"基于Java的视频流处理(原创)"是一个使用Java技术进行实时视频流处理的应用。这个项目的核心是实现一个基本的哈哈镜效果,并结合了第三方库来完成人脸检测以及对人脸的图像遮挡功能。在创新实践项目课...

    java零基础自学 之 JavaIO流处理

    字节流处理8位的字节,适用于二进制文件,如图片、音频等。 2. **IO类的分类** Java的IO类主要分为两大类:字符流(Reader和Writer)和字节流(InputStream和OutputStream)。Reader和Writer是字符流的基类,...

    Java8并行流中自定义线程池操作示例

    Java8并行流中自定义线程池操作示例 Java8并行流中自定义线程池操作示例主要介绍了Java8并行流中自定义线程池操作,结合实例形式分析了并行流的相关概念、定义及自定义线程池的相关操作技巧。 1. 概览 Java8引入了...

    Java字符流与字节流区别

    Java 内用 Unicode 编码存储字符,字符流处理类负责将外部的其他编码的字符流和 java 内 Unicode 字符流之间的转换。而类 InputStreamReader 和 OutputStreamWriter 处理字符流和字节流的转换。字符流(一次可以处理...

    基于red5 dsj 的java 处理视频流和一些疑问

    标题 "基于red5 dsj 的java 处理视频流和一些疑问" 提示我们讨论的是一个使用Red5和DSJ(可能是指“Digital Signal Jockey”或某种特定工具)进行Java视频流处理的项目。Red5是一个开源的流媒体服务器,能够处理音频...

    java实现流媒体播放

    最后,Java流媒体播放程序应具备良好的错误处理机制,如网络中断后的恢复、媒体文件格式不支持时的提示等。同时,考虑到不同的操作系统和硬件环境,程序还需要考虑跨平台的兼容性。 总的来说,Java 实现流媒体播放...

    Java的异常处理和IO流

    Java的异常处理与IO流是Java编程中两个重要的概念,它们在软件开发中扮演着至关重要的角色。本文将深入探讨这两个主题,以便更好地理解和应用它们。 ### 一、Java异常处理 #### 1.1 什么是异常 异常是指在程序...

    java数据流总结

    java数据流总结,java io流总结,节点流和处理流 java数据流总结,java io流总结,节点流和处理流 java数据流总结,java io流总结,节点流和处理流

    java 视频流读写

    Java 视频流读写是Java编程中涉及多媒体处理的一部分,主要目的是为了在应用程序中处理、播放或存储视频数据。Java Media Framework (JMF) 是Java平台上的一个关键组件,用于处理音频、视频和流媒体内容。在这个"JMF...

    Java+8实战_Java8_java8_

    3. **流(Stream)**:流API是Java 8处理集合数据的新方式,它提供了一种声明性处理数据的方式,适合进行聚合操作。流可以与lambda表达式结合,实现高效的并行计算。 4. **方法引用来替代lambda**:在某些情况下,...

    java_io流的处理

    ### Java IO流处理详解 #### I/O流概述 在Java编程中,输入/输出(I/O)处理是一项核心技能,涉及从键盘读取数据、向屏幕输出数据、从文件读写数据以及在网络连接上进行读写操作。Java将这些不同的输入输出源抽象...

    Java字符流和字节流

    字节流处理的是8位的字节数据,而字符流处理的是16位的Unicode字符。这两种流都有各自的特点和应用场景。 #### 二、字节流 字节流是最基本的数据传输方式,适用于任何类型的数据传输。在Java中,所有字节流类都是`...

    IO流 javaio java 流

    字符流处理单个字符,而字节流处理8位的字节序列。它们又可以进一步细分为输入流(InputStream/Reader)和输出流(OutputStream/Writer),分别用于数据的读取和写入。 1. 字节流: - 字节输入流(InputStream):...

    JAVA IO流技术

    4. Java流的细分: - 流的方向:输入流用于从数据源读取数据,输出流用于向目标写入数据。 - 处理数据单元:字节流按字节进行读取(InputStream、OutputStream),字符流按字符进行读取(Reader、Writer)。 - ...

    Java流(文件读写操作)

    ### Java流(文件读写操作) #### 一、流的分类 Java中处理文件和数据时,使用流的概念来进行操作。根据不同的标准,流可以分为几种类型。 ##### 1. 按数据流动方向 - **输入流**:主要用于从数据源读取数据。输入...

    java_IO流的处理.ppt

    Java IO流处理是Java编程中一个非常重要的概念,它用于在不同数据源之间传输数据,如文件、网络、内存等。在Java中,IO流分为两大类:输入流(Input Stream)和输出流(Output Stream),它们分别用于数据的读取和...

    java输入输出流与文件处理

    字节流适用于处理二进制数据,而字符流更擅长处理文本数据,两者的区别在于数据处理的基本单位,字节流处理的是8位的数据,而字符流处理的是16位的Unicode字符。 #### 总结 Java的输入输出流与文件处理机制为...

    基于Java的流媒体视频直播服务器设计与实现

    本项目聚焦于“基于Java的流媒体视频直播服务器设计与实现”,这涉及到多个IT领域的知识,包括音视频处理、服务器架构、Java编程以及运维等。 首先,我们要理解流媒体的概念。流媒体是指在互联网上传输的连续多媒体...

    java 输入输出流

    2. **字符流**:字符流处理Unicode字符数据,分为字符输入流(Reader)和字符输出流(Writer)。Reader的基类是`java.io.Reader`,Writer的基类是`java.io.Writer`。比如,`FileReader`用于读取字符文件,`...

Global site tag (gtag.js) - Google Analytics