java8的stream流
1.流的基本概念
2.流的使用
转化流
2.1获取流
处理流
筛选filter--对象
去重distinct
截取(前N个)
跳过(除了前N个)
排序sorted:
映射
合并多个流
是否存在元素匹配:anyMatch
是否所有元素匹配:allMatch
是否所有元素都不匹配:noneMatch
获取任一元素findAny
获取第一个元素findFirst
归约reduce
数值流的使用
将普通流转换成数值流
数值计算
计算
整合流
归约
分组
分区
并行流式数据处理parallelStream()
来源:
java8的stream流
1.流的基本概念
- 优点:
- 简化代码;
- 由于操作过程完全由Java处理,因此它可以根据当前硬件环境选择最优的方法处理,速度快,不会出错;
- 特点:
- 1.只能遍历一次
我们可以把流想象成一条流水线,流水线的源头是我们的数据源(一个集合),数据源中的元素依次被输送到流水线上,我们可以在流水线上对元素进行各种操作。一旦元素走到了流水线的另一头,那么这些元素就被“消费掉了”,我们无法再对这个流进行操作。当然,我们可以从数据源那里再获得一个新的流重新遍历一遍。
- 采用内部迭代方式
若要对集合进行处理,则需我们手写处理代码,这就叫做外部迭代。而要对流进行处理,我们只需告诉流我们需要什么结果,处理过程由流自行完成,这就称为内部迭代。
- 1.只能遍历一次
- 流的操作过程:
- 1.准备一个数据源;
- 2.执行中间操作;
中间操作可以有多个,它们可以串连起来形成流水线。 - 3.执行终端操作;
执行终端操作后本次流结束,你将获得一个执行结果。
2.流的使用
转化流
2.1获取流
- 集合:
list.stream();
- 数组
Arrays.stream(names);
- 普通值
Stream<String> stream = Stream.of("chaimm","peter","john");
- 文件
try(Stream lines = Files.lines(Paths.get(“文件路径名”),Charset.defaultCharset())){
//可对lines做一些操作
}catch(IOException e){
}
//PS:Java7简化了IO操作,把打开IO操作放在try后的括号中即可省略关闭IO的代码。
处理流
筛选filter--对象
- 定义:filter函数接收一个Lambda表达式作为参数,该表达式返回boolean,在执行过程中,流将元素逐一输送给filter,并筛选出执行结果为true的元素。
- 实质:将符合条件的对象过滤出来;
- 案例:筛选出所有学生:
List<Person> result = list.stream()
.filter(Person::isStudent) //使用双冒号,调用这个对象的isStudent参数
// .filter(student -> "武汉大学".equals(student.getSchool()))
.collect(toList());//符合条件的对象组成list
去重distinct
- 定义:去掉重复的结果
- 实质:去掉重复的结果
- 案例:去重
List<Person> result = list.stream()
.distinct() //去重
.collect(toList());
截取(前N个)
- 定义:截取流的前N个元素
- 实质:截取流的前N个元素
- 案例:截取流的前3个元素
List<Person> result = list.stream()
.limit(3)
.collect(toList());
跳过(除了前N个)
- 定义:跳过流的前n个元素
- 实质:跳过流的前n个元素
- 案例:跳过流的前3个元素
List<Person> result = list.stream()
.skip(3)
.collect(toList());
排序sorted:
- 定义:该操作用于对流中元素进行排序,sorted要求待比较的元素必须实现Comparable接口,如果没有实现也不要紧,我们可以将比较器作为参数传递给sorted(Comparator
comparator) - 实质:对对象排序;
- 案例:筛选出专业为土木工程的学生,并按年龄从小到大排序,筛选出年龄最小的两个学生
List<Student> sortedCivilStudents = students.stream()
.filter(student -> "土木工程".equals(student.getMajor())) //筛选
.sorted((s1, s2) -> s1.getAge() - s2.getAge()) //排序(从小到大)
.limit(2) //前俩个
.collect(Collectors.toList());//组成list
方法2:
public Comparator<DeptLevelDto> deptSeqComparator = Comparator.comparingInt(SysDept::getSeq);
rootList.sort(deptSeqComparator);
方法3:
list.sort((o1,o2)->{return xxx;})
映射
- 定义:对流中的每个元素执行一个函数,使得元素转换成另一种类型输出。流会将每一个元素输送给map函数,并执行map中的Lambda表达式,最后将执行结果存入一个新的流中。
- 实质:执行对象中一个方法,将结果组成list;
- 案例:获取每个人的姓名(实则是将Perosn类型转换成String类型):
List<String> result = list.stream()
.map(Person::getName)
.collect(toList());
合并多个流
- 定义:合并多个流(list)
- 实质:将list中每个字段按照条件split,组成的所有的流和为一个流
- 案例:
list.stream() //首先将list变成流:
.map(line->line.split(" ")) //按空格分词[00,00],[1111],[222,2],[3333],[4444],会成为5个list
.flagmap(Arrays::stream) //将小流合并成一个大流:5个list整合为1个list
.distinct() //* 去重
.collect(toList());
原数据:[00 00, 1111, 222 2, 3333, 4444]
新数据:[00, 1111, 222, 2, 3333, 4444]
是否存在元素匹配:anyMatch
- 定义:anyMatch用于判断流中是否存在至少一个元素满足指定的条件,这个判断条件通过Lambda表达式传递给anyMatch,执行结果为boolean类型。
- 实质:list中存在对象符合条件
- 案例:判断list中是否有学生
boolean result = list.stream()
.anyMatch(Person::isStudent);
是否所有元素匹配:allMatch
- 定义:allMatch用于判断流中的所有元素是否都满足指定条件,这个判断条件通过Lambda表达式传递给anyMatch,执行结果为boolean类型。
- 实质:list中所有对象都符合条件
- 案例:判断是否所有人都是学生
boolean result = list.stream()
.allMatch(Person::isStudent);
是否所有元素都不匹配:noneMatch
- 定义:noneMatch与allMatch恰恰相反,它用于判断流中的所有元素是否都不满足指定条件:
- 实质:list中所有对象都不符合条件
- 案例:判断是否所有人都不是学生
boolean result = list.stream() .noneMatch(Person::isStudent);
获取任一元素findAny
- 定义:findAny能够从流中随便选一个元素出来,它返回一个Optional类型的元素。
- 实质:从流中随便选一个元素
- 案例:
Optional<Person> person = list.stream().findAny();
Optional--java8新类 为知:Optional-java8.md
获取第一个元素findFirst
- 定义:获取第一个元素findFirst
- 实质:获取第一个元素findFirst
- 案例:
Optional<Person> person = list.stream()
.findFirst();
//可以.orElse(T)来返回第一个;中间的其他条件也可以使用;
归约reduce
- 定义:归约是将集合中的所有元素经过指定运算,折叠成一个元素输出,如:求最值、平均数等,这些操作都是将一个集合的元素折叠成一个元素输出。
- 实质:对参数进行计算,返回结果
- 案例:求和
- 方法1:自定义Lambda表达式实现求和
int age = list.stream().reduce(0, (person1,person2)->person1.getAge()+person2.getAge());
//reduce的第一个参数表示初试值为0;
//reduce的第二个参数为需要进行的归约操作,它接收一个拥有两个参数的Lambda表达式,reduce会把流中的元素两两输给Lambda表达式,最后将计算出累加之和。
- 方法2:使用Integer.sum函数求和
Integer提供了sum函数
int age = list.stream().reduce(0, Integer::sum);
//Integer类还提供了min、max等一系列数值操作,当流中元素为数值类型时可以直接使用。
数值流的使用
采用reduce进行数值操作会涉及到基本数值类型和引用数值类型之间的装箱、拆箱操作,因此效率较低。
当流操作为纯数值操作时,使用数值流能获得较高的效率。(效率高)
将普通流转换成数值流
StreamAPI提供了三种数值流:IntStream
、DoubleStream
、LongStream
,也提供了将普通流转换成数值流的三种方法:mapToInt
、mapToDouble
、mapToLong
。
- 案例:将Person中的age转换成数值流
IntStream stream = list.stream()
.mapToInt(Person::getAge);
数值计算
每种数值流都提供了数值计算函数,如max、min、sum等。
- 案例:找出最大的年龄
OptionalInt maxAge = list.stream()
.mapToInt(Person::getAge)
.max();
计算
- 案例:求和:
students.stream()
方法1: .mapToInt(Student::getAge).sum();
方法2: .map(Student::getAge).reduce(0, (a, b) -> a + b);
方法3: .map(Student::getAge) .reduce(0, Integer::sum);
方法4: .map(Student::getAge) .reduce(Integer::sum);
由于数值流可能为空,并且给空的数值流计算最大值是没有意义的,因此max函数返回OptionalInt
,它是Optional的一个子类,能够判断流是否为空,并对流为空的情况作相应的处理(.orElse(-1)--注意,返回的是int,不会有Integer)。
此外,mapToInt、mapToDouble、mapToLong进行数值操作后的返回结果分别为:OptionalInt、OptionalDouble、OptionalLong
整合流
对处理结果的封装,例如collect(Collectors.toList())
归约
- 定义:收集器也提供了相应的归约操作,但是与reduce在内部实现上是有区别的,收集器更加适用于可变容器上的归约操作,这些收集器广义上均基于
Collectors.reducing()
实现。 - 实质:对list计算,返回一个结果
- 案例:
- 总数(size)
1.求学生的总人数(size)
方法1:
long count = students.stream().collect(Collectors.counting());
方法2:
long count = students.stream().count();
- 最大值和最小值
求最大年龄
方法1:
Optional<Student> olderStudent = students.stream().collect(Collectors.maxBy((s1, s2) -> s1.getAge() - s2.getAge()));
方法2:
Optional<Student> olderStudent2 = students.stream().collect(Collectors.maxBy(Comparator.comparing(Student::getAge)));// 进一步简化
求最小年龄
Optional<Student> olderStudent3 = students.stream().collect(Collectors.minBy(Comparator.comparing(Student::getAge)));
- 对象参数计算
求和:summingInt
、summingLong
、summingDouble
平均值:averagingInt
。
求年龄总和
int totalAge4 = students.stream().collect(Collectors.summingInt(Student::getAge));
一次性得到元素个数、总和、均值、最大值、最小值:
summarizingInt
summarizingLong
summarizingDouble
IntSummaryStatistics statistics = students.stream().collect(Collectors.summarizingInt(Student::getAge));
//IntSummaryStatistics{count=10, sum=220, min=20, average=22.000000, max=24}
- 字符串拼接
String names = students.stream().map(Student::getName).collect(Collectors.joining());
// 输出:孔明伯约玄德云长翼德元直奉孝仲谋鲁肃丁奉
String names = students.stream().map(Student::getName).collect(Collectors.joining(","));
// 输出:孔明,伯约,玄德,云长,翼德,元直,奉孝,仲谋,鲁肃,丁奉
分组
- 定义:在数据库操作中,我们可以通过GROUP BY关键字对查询到的数据进行分组,java8的流式处理也为我们提供了这样的功能Collectors.groupingBy来操作集合。
- 实质:分组(可以多级)
- 案例:
- 一级分组
按学校对上面的学生进行分组
- 一级分组
Map<String, List<Student>> groups = students.stream().collect(Collectors.groupingBy(Student::getSchool));
- 多级分组
Map<String, Map<String, List<Student>>> groups2 = students.stream().collect(
Collectors.groupingBy(Student::getSchool, // 一级分组,按学校
Collectors.groupingBy(Student::getMajor))); // 二级分组,按专业
- 分组数据操作
实际上在groupingBy的第二个参数不是只能传递groupingBy,还可以传递任意Collector类型,比如我们可以传递一个Collector.counting,用以统计每个组的个数:
Map<String, Long> groups = students.stream().collect(Collectors.groupingBy(Student::getSchool, Collectors.counting()));
如果我们不添加第二个参数,则编译器会默认帮我们添加一个Collectors.toList()。
分区
- 定义:分区可以看做是分组的一种特殊情况,在分区中key只有两种情况:true或false,目的是将待分区集合按照条件一分为二,java8的流式处理利用ollectors.partitioningBy()方法实现分区,该方法接收一个谓词;
- 优势:分区相对分组的优势在于,我们可以同时得到两类结果,在一些应用场景下可以一步得到我们需要的所有结果,比如将数组分为奇数和偶数。
- 实质:按条件分为两组(是/否)
- 案例:学生和非武大学生
Map<Boolean, List<Student>> partition = students.stream().collect(Collectors.partitioningBy(student -> "武汉大学".equals(student.getSchool())));
并行流式数据处理parallelStream()
- 定义:
流式处理中的很多都适合采用 分而治之 的思想,从而在处理集合较大时,极大的提高代码的性能,java8的设计者也看到了这一点,所以提供了 并行流式处理。上面的例子中我们都是调用stream()方法来启动流式处理,java8还提供了parallelStream()来启动并行流式处理,parallelStream()本质上基于java7的Fork-Join框架实现,其默认的线程数为宿主机的内核数。
启动并行流式处理虽然简单,只需要将stream()替换成parallelStream()即可,但既然是并行,就会涉及到多线程安全问题,所以在启用之前要先确认并行是否值得(并行的效率不一定高于顺序执行),另外就是要保证线程安全。此两项无法保证,那么并行毫无意义,毕竟结果比速度更加重要,以后有时间再来详细分析一下并行流式数据处理的具体实现和最佳实践。
在list使用parallelStream的时候,经常会出现异常,经过测试发现是list的原因,ArrayList是不安全的,若是使用并行处理需要对list进行处理
list=Collections.synchronizedList(list);
list.parallelStream().... 就不会异常了;