简介
现在来看看流式操作吧,JDK8的流式操作是真的香啊,你还不会你就out了
环境
- JDK8
介绍
jdk8引进来的lambda语法是新语法,里面融进了python和c#的部分语法,stream()函数是主要用于集合而言的,对于数组就没效果
学习Stream之前,首先要了解他的操作符包含中间操作符和终止操作符
中间操作符对于数据流来说,中间操作符在执行制定处理程序后,数据流依然可以传递给下一级的操作符。
中间操作符包含8种(排除了parallel,sequential,这两个操作并不涉及到对数据流的加工操作):
终止操作符map(mapToInt,mapToLong,mapToDouble) 转换操作符,把比如A->B,这里默认提供了转int,long,double的操作符。flatmap(flatmapToInt,flatmapToLong,flatmapToDouble) 拍平操作比如把 int[]{2,3,4} 拍平 变成 2,3,4 也就是从原来的一个数据变成了3个数据,这里默认提供了拍平成int,long,double的操作符。limit 限流操作,比如数据流中有10个 我只要出前3个就可以使用。distint 去重操作,对重复元素去重,底层使用了equals方法。filter 过滤操作,把不想要的数据过滤。peek 挑出操作,如果想对数据进行某些操作,如:读取、编辑修改等。skip 跳过操作,跳过某些元素。sorted(unordered) 排序操作,对元素排序,前提是实现Comparable接口,当然也可以自定义比较器。
数据经过中间加工操作,就轮到终止操作符上场了;终止操作符就是用来对数据进行收集或者消费的,数据到了终止操作这里就不会向下流动了,终止操作符只能使用一次。
collect 收集操作,将所有数据收集起来,这个操作非常重要,官方的提供的Collectors 提供了非常多收集器,可以说Stream 的核心在于Collectors。count 统计操作,统计最终的数据个数。findFirst、findAny 查找操作,查找第一个、查找任何一个 返回的类型为Optional。noneMatch、allMatch、anyMatch 匹配操作,数据流中是否存在符合条件的元素 返回值为bool 值。min、max 最值操作,需要自定义比较器,返回数据流中最大最小的值。reduce 规约操作,将整个数据流的值规约为一个值,count、min、max底层就是使用reduce。forEach、forEachOrdered 遍历操作,这里就是对最终的数据进行消费了。toArray 数组操作,将数据流的元素转换成数组。
这里只介绍了Stream,并没有涉及到IntStream、LongStream、DoubleStream,这三个流实现了一些特有的操作符,我将在后续文章中介绍到。
说了这么多,只介绍这些操作符还远远不够;俗话说,实践出真知。那么,Let‘s go。
1、Stream 流概念
Stream 流的使用总是按照一定的步骤进行,可以抽象出下面的使用流程。
数据源(source) -> 数据处理/转换(intermedia) -> 结果处理(terminal )
1.1、数据源
数据源(source)也就是数据的来源,可以通过多种方式获得 Stream 数据源,下面列举几种常见的获取方式。
Collection.stream(); 从集合获取流。
Collection.parallelStream(); 从集合获取并行流。
Arrays.stream(T array) or Stream.of(); 从数组获取流。
BufferedReader.lines(); 从输入流中获取流。
IntStream.of() ; 从静态方法中获取流。
Stream.generate(); 自己生成流
1.2、数据处理
数据处理/转换(intermedia)步骤可以有多个操作,这步也被称为intermedia(中间操作)。在这个步骤中不管怎样操作,它返回的都是一个新的流对象,原始数据不会发生任何改变,而且这个步骤是惰性计算处理的,也就是说只调用方法并不会开始处理,只有在真正的开始收集结果时,中间操作才会生效,而且如果遍历没有完成,想要的结果已经获取到了(比如获取第一个值),会停止遍历,然后返回结果。惰性计算可以显著提高运行效率。
//将数组转成集合
List<String> nameList= Arrays.asList("aaf","bbccdd","cdsc","dsd");
/*1、筛选名字长度为4的
* 2、名字前面拼接This is
* 3、遍历输出
* */
nameList.stream()
.filter(name->name.length()==4)
.map(name->"This is "+name)
.forEach(name-> System.out.println(name));
数据处理/转换操作自然不止是上面演示的过滤 filter 和 map映射两种,另外还有 map (mapToInt, flatMap 等)、 filter、 distinct、 sorted、 peek、 limit、 skip、 parallel、 sequential、 unordered 等。
1.3、收集结果
结果处理(terminal )是流处理的最后一步,执行完这一步之后流会被彻底用尽,流也不能继续操作了。也只有到了这个操作的时候,流的数据处理/转换等中间过程才会开始计算,也就是上面所说的惰性计算。结果处理也必定是流操作的最后一步。
常见的结果处理操作有 forEach、 forEachOrdered、 toArray、 reduce、 collect、 min、 max、 count、 anyMatch、 allMatch、 noneMatch、 findFirst、 findAny、 iterator 等。
//数组转成集合
List<String> nameList = Arrays.asList("Darcy", "Chris", "Linda", "Sid", "Kim", "Jack", "Poul", "Peter");
//将集合的元素变成大写的
List<String> upperCaseName=nameList.stream()
.map(String::toUpperCase)
.collect(Collectors.toList());
upperCaseName.forEach(name-> System.out.println(name));
1.4、short-circuiting
有一种 Stream 操作被称作 short-circuiting ,它是指当 Stream 流无限大但是需要返回的 Stream 流是有限的时候,而又希望它能在有限的时间内计算出结果,那么这个操作就被称为short-circuiting。例如 findFirst 操作。
2、Stream流的使用
Stream 流在使用时候总是借助于 Lambda 表达式进行操作,Stream 流的操作也有很多种方式,下面列举的是常用的 11 种操作。
2.1、Stream流的获取
大概是以下几种获取流的方式
public static void createStream() throws FileNotFoundException {
//数组转集合
List<String> nameList = Arrays.asList("Darcy", "Chris", "Linda", "Sid", "Kim", "Jack", "Poul", "Peter");
//数组
String[] nameArr = {"Darcy", "Chris", "Linda", "Sid", "Kim", "Jack", "Poul", "Peter"};
//集合获取串行Stream流
Stream<String> nameListStream=nameList.stream();
//集合获取并行Stream流
Stream<String> nameListStream2=nameList.parallelStream();
//数组获取Stream流
Stream<String> nameAttrStream=Stream.of(nameArr);
//数组获取Stream流
Stream<String> nameAttrStream2=Arrays.stream(nameArr);
//文件流获取Stream流
BufferedReader bufferedReader=new BufferedReader(new FileReader("1.txt"));
Stream<String> linesStream=bufferedReader.lines();
//从静态方法获取流操作
IntStream rangeStream=IntStream.range(1,10);
rangeStream.limit(10).forEach(num-> System.out.println(num+","));
}
2.2、forEach
遍历
List<Integer> numberList = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9);
numberList.stream().forEach(number -> System.out.println(number+","));
2.3、map/ flatMap
使用 map 把对象一对一映射成另一种对象或者形式。相当于:key是原来的值,value是处理之后的值 map是一对一映射
//数组转集合
List<Integer> numberList = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9);
//映射成2倍数字
List<Integer> collect=numberList.stream().map(num->num*2).collect(Collectors.toList());
collect.forEach(num-> System.out.println(num));
有时候会出现一对多的映射,这时候就用flatmap
2.4、filter
使用 filter 进行数据筛选,挑选出想要的元素,下面的例子演示怎么挑选出偶数数字。
//数组转集合
List<Integer> numberList = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9);
//筛选偶数数字
List<Integer> collect=numberList.stream().filter(num->num%2==0).collect(Collectors.toList());
collect.forEach(num-> System.out.println(num));
2.5、findFirst
findFirst 可以查找出 Stream 流中的第一个元素,它返回的是一个 Optional 类型
List<Integer> numberList = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9);
Optional<Integer> firstNumber=numberList.stream().findFirst();
//orElse表示如果结果为空就用-1表示
System.out.println(firstNumber.orElse(-1));
findFirst 方法在查找到需要的数据之后就会返回不再遍历数据了,也因此 findFirst 方法可以对有无限数据的 Stream 流进行操作,也可以说 findFirst 是一个 short-circuiting 操作。
2.6、collect / toArray
Stream 流可以轻松的转换为其他结构
//stream转换为其他数据结构
List<Integer> numberList=Arrays.asList(1,2,3,4,5);
//to array
Integer[] toArray=numberList.stream().toArray(Integer[]::new);
System.out.println(toArray);
//to List
List<Integer> integerList=numberList.stream().collect(Collectors.toList());
System.out.println(integerList);
//to set
Set<Integer> integerSet=numberList.stream().collect(Collectors.toSet());
System.out.println(integerSet);
//to String
String toString=numberList.stream().map(num->String.valueOf(num)).collect(Collectors.joining());
System.out.println("toString:"+toString);
//to String split by ,
String toString2=numberList.stream().map(num->String.valueOf(num)).collect(Collectors.joining(","));
System.out.println("toString2:"+toString2);
2.7、limit / skip
limit:获取前面多少个元素 skip:跳过前面多少个元素
// 生成自己的随机数流
List<Integer> ageList = Arrays.asList(11, 22, 13, 14, 25, 26);
//取前面前3个
ageList.stream().limit(3).forEach(System.out::println);
//跳过前面3个
ageList.stream().skip(3).forEach(System.out::println);
2.8、Statistics
数学统计功能,求一组数组的最大值、最小值、个数、数据和、平均数等。
//数学计算测试
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6);
//mapToInt是将元素映射成int类型
IntSummaryStatistics stats=list.stream().mapToInt(x->x).summaryStatistics();
System.out.println("最大值:"+stats.getMax());
System.out.println("最小值:"+stats.getMin());
System.out.println("个数:"+stats.getCount());
System.out.println("和:"+stats.getSum());
System.out.println("平均数:"+stats.getAverage());
2.9、groupingBy
分组聚合功能,和数据库的 Group by 的功能一致。
List<Integer> ageList=Arrays.asList(11,12,13,22,23,24);
Map<String,List<Integer>> ageGroupMap=ageList.stream().collect(Collectors.groupingBy(age->String.valueOf(age/10)));
ageGroupMap.forEach((k,v)->{
System.out.println("年龄:"+k+"0多岁的有:"+v);
});
2.10、partitioningBy
按某个条件分组 给一组年龄,分出成年人和未成年人
//按某个条件分组,给一组年龄,分出成年人和未成年人
List<Integer> ageList=Arrays.asList(12,23,45,17,58,35);
Map<Boolean,List<Integer>> ageMap=ageList.stream().collect(Collectors.partitioningBy(age->age>18));
System.out.println("未成年人:"+ageMap.get(false));
System.out.println("成年人:"+ageMap.get(true));
2.11、进阶 - 自己生成 Stream 流
// 生成自己的随机数流
Random random = new Random();
Stream<Integer> generateRandom = Stream.generate(random::nextInt);
generateRandom.limit(5).forEach(System.out::println);
// 生成自己的 UUID 流
Stream<UUID> generate = Stream.generate(UUID::randomUUID);
generate.limit(5).forEach(System.out::println);
3、并行计算
获取 Stream 流时可以使用 parallelStream 方法代替 stream 方法以获取并行处理流,并行处理可以充分的发挥多核优势,而且不增加编码的复杂性。
/**
* 并行计算
*/
@Test
public void main() {
// 生成自己的随机数流,取一千万个随机数
Random random = new Random();
Stream<Integer> generateRandom = Stream.generate(random::nextInt);
List<Integer> numberList = generateRandom.limit(10000000).collect(Collectors.toList());
// 串行 - 把一千万个随机数,每个随机数 * 2 ,然后求和
long start = System.currentTimeMillis();
int sum = numberList.stream()
.map(number -> number * 2)
.mapToInt(x -> x)
.sum();
long end = System.currentTimeMillis();
System.out.println("串行耗时:"+(end - start)+"ms,和是:"+sum);
// 并行 - 把一千万个随机数,每个随机数 * 2 ,然后求和
start = System.currentTimeMillis();
sum = numberList.parallelStream()
.map(number -> number * 2)
.mapToInt(x -> x)
.sum();
end = System.currentTimeMillis();
System.out.println("并行耗时:"+(end - start)+"ms,和是:"+sum);
}
结果:
串行耗时:1005ms,和是:481385106
并行耗时:47ms,和是:481385106