目录
14.mapToInt / mapToDouble / mapToLong
16.allMatch / anyMatch / noneMatch 匹配相关
6.5 并行 Stream 流底层原理分析(Fork/Join 框架)
一 ,stream流
1.1.特性
1.Stream流不是一种数据结构,不保存数据,它只是在原数据集上定义了一组操作。
2.这些操作是惰性的,即每当访问到流中的一个元素,才会在此元素上执行这一系列操作。
3.Stream不保存数据,故每个Stream流只能使用一次。
所以这边有两个概念:流、管道。元素流在管道中经过中间操作的处理,最后由最终操作得到前面处理的结果。这里有2个操作:中间操作、最终操作。
中间操作:返回结果都是Stream,故可以多个中间操作叠加。
终止操作:用于返回我们最终需要的数据,只能有一个终止操作。
使用Stream流,可以清楚地知道我们要对一个数据集做何种操作,可读性强。而且可以很轻松地获取并行化Stream流,不用自己编写多线程代码,可以更加专注于业务逻辑。默认情况下,从有序集合、生成器、迭代器产生的流或者通过调用Stream.sorted产生的流都是有序流,有序流在并行处理时会在处理完成之后恢复原顺序。无限流的存在,侧面说明了流是惰性的,即每当用到一个元素时,才会在这个元素上执行这一系列操作。
二.使用步骤
1.创建Stream
2.转换Stream,每次转换原有Stream对象不改变,返回一个新的Stream对象(可以有多次转换)
3.对Stream进行聚合操作,获取想要的结果
2. 流的生成方法
1.Collection接口的stream()或parallelStream()方法
2.静态的Stream.of()、Stream.empty()方法
3.Arrays.stream(array, from, to)
4.静态的Stream.generate()方法生成无限流,接受一个不包含引元的函数
5.静态的Stream.iterate()方法生成无限流,接受一个种子值以及一个迭代函数
6.Pattern接口的splitAsStream(input)方法
7.静态的Files.lines(path)、Files.lines(path, charSet)方法
8.静态的Stream.concat()方法将两个流连接起来
3.获取Stream流的两种方式
1.所有的 Collection 集合都可以通过 .stream() 方法来获取流;
2.使用 Stream 接口的 .of() 静态方法,可以获取流。
/**
* TODO 获取 Stream 流的两种方式
*
* @author liuzebiao
* @Date 2020-1-7 17:09
*/
public class getStreamDemo {
public static void main(String[] args) {
//方式1:根据Collection获取流
//Collection接口中有一个默认的方法:default Stream<E> stream()
//1.List获取流
List<String> list = new ArrayList<>();
Stream<String> stream01 = list.stream();
//2.Set获取流
Set<String> set = new HashSet<>();
Stream<String> stream02 = set.stream();
//3.Map获取流
//Map 并没有继承自 Collection 接口,所有无法通过该 map.stream()获取流。但是可用通过如下三种方式获取:
Map<String,String> map = new HashMap<>();
Stream<String> stream03 = map.keySet().stream();
Stream<String> stream04 = map.values().stream();
Stream<Map.Entry<String, String>> stream05 = map.entrySet().stream();
//方式2:Stream中的静态方法of获取流
// static<T> Stream<T> of(T... values)
// T... values:可变参数,实际原理就是可变数组(传递String数组进去)
//1.字符串获取流
Stream<String> stream06 = Stream.of("aa", "bb", "cc");
//2.数组类型(基本类型除外)
String[] strs = {"aa","bb","cc"};
Stream<String> stream07 = Stream.of(strs);
//3.基本数据类型的数组
int[] arr = {1,2,3,4};
//看着没报错,但是看到返回值是 int[],这是 Stream流把整个数组看做一个元素来操作,而不是操作数组中的int元素(这样子是不行的!!!)
Stream<int[]> stream08 = Stream.of(arr);
}
}
三、 流的Intermediate方法(中间操作)
1.filter(Predicate) :将结果为false的元素过滤掉
2.map(fun) :转换元素的值,可以用方法引元或者lambda表达式
3.flatMap(fun) :若元素是流,将流摊平为正常元素,再进行元素转换
4.limit(n) :保留前n个元素
5.skip(n) :跳过前n个元素
6.distinct() :剔除重复元素
7.sorted() :将Comparable元素的流排序
8.sorted(Comparator) :将流元素按Comparator排序
9.peek(fun) :流不变,但会把每个元素传入fun执行,可以用作调试
四、 流的Terminal方法(终结操作)
(1)约简操作
1.reduce(fun) :从流中计算某个值,接受一个二元函数作为累积器,从前两个元素开始持续应用它,累积器的中间结果作为第一个参数,流元素作为第二个参数
2.reduce(a, fun) :a为幺元值,作为累积器的起点
3.reduce(a, fun1, fun2) :与二元变形类似,并发操作中,当累积器的第一个参数与第二个参数都为流元素类型时,可以对各个中间结果也应用累积器进行合并,但是当累积器的第一个参数不是流元素类型而是类型T的时候,各个中间结果也为类型T,需要fun2来将各个中间结果进行合并
(2)收集操作
1.iterator():
2.forEach(fun):
3.forEachOrdered(fun) :可以应用在并行流上以保持元素顺序
4.toArray():
5.toArray(T[] :: new) :返回正确的元素类型
6.collect(Collector):
7.collect(fun1, fun2, fun3) :fun1转换流元素;fun2为累积器,将fun1的转换结果累积起来;fun3为组合器,将并行处理过程中累积器的各个结果组合起来
(3)查找与收集操作
1.max(Comparator):返回流中最大值
2.min(Comparator):返回流中最小值
3.count():返回流中元素个数
4.findFirst() :返回第一个元素
5.findAny() :返回任意元素
6.anyMatch(Predicate) :任意元素匹配时返回true
7.allMatch(Predicate) :所有元素匹配时返回true
8.noneMatch(Predicate) :没有元素匹配时返回true
五.常规操作
0.collect(toList())
由Stream 里的值生成一个列表,是一个及早求值操作
很多Stream 操作都是惰性求值,因此调用Stream 一系列方法之后
还需最后再调用一个类似collect 的及早求值方法返回集合
开篇的例子:(再将符合要求的字符串放到一个新的集合里)
使用filter过滤器:遍历数据并检查其中的元素
package org.xxxx.demo01;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
public class Demo01 {
public static void main(String[] args) {
// 创建集合
List<String> list = new ArrayList<>();
// 添加元素
list.add("sdf");
list.add("a");
list.add("asdf");
list.add("d");
list.add("basdfgh");
// 统计长度大于2的
long count = list.stream().filter((s) -> s.length() > 2).count();
// 将符合要求的放入集合
List<String> list2 = list.stream().filter((s) -> s.length() > 2).collect(Collectors.toList());
System.out.println(count);
list2.forEach(System.out :: println);
}
}
1.收集 Stream 流中的数据到集合中
//1.收集数据到list集合中
stream.collect(Collectors.toList())
//2.收集数据到set集合中
stream.collect(Collectors.toSet())
//3.收集数据到指定的集合中
Collectors.toCollection(Supplier<C> collectionFactory)
stream.collect(Collectors.joining())
/**
* 收集Stream流中的数据到集合中
* 备注:切记Stream流只能被消费一次,流就失效了
* 如下只是示例代码
*/
public class CollectDataToCollection{
public static void main(String[] args) {
//Stream 流
Stream<String> stream = Stream.of("aaa", "bbb", "ccc", "bbb");
//收集流中的数据到集合中
//1.收集流中的数据到 list
List<String> list = stream.collect(Collectors.toList());
System.out.println(list);
//2.收集流中的数据到 set
Set<String> collect = stream.collect(Collectors.toSet());
System.out.println(collect);
//3.收集流中的数据(ArrayList)(不收集到list,set等集合中,而是)收集到指定的集合中
ArrayList<String> arrayList = stream.collect(Collectors.toCollection(ArrayList::new));
System.out.println(arrayList);
//4.收集流中的数据到 HashSet
HashSet<String> hashSet = stream.collect(Collectors.toCollection(HashSet::new));
System.out.println(hashSet);
}
}
2.收集 Stream 流中的数据到数组中
//1.使用无参,收集到数组,返回值为 Object[](Object类型将不好操作)
Object[] toArray();
//2.使用有参,可以指定将数据收集到指定类型数组,方便后续对数组的操作
<A> A[] toArray(IntFunction<A[]> generator);
/**
* 收集Stream流中的数据到数组中
* 备注:切记Stream流只能被消费一次,流就失效了
* 如下只是示例代码
*/
public class CollectDataToArray{
public static void main(String[] args) {
//Stream 流
Stream<String> stream = Stream.of("aaa", "bbb", "ccc", "bbb");
//2.1 使用 toArray()无参
Object[] objects = stream.toArray();
for (Object o: objects) {//此处无法使用.length() 等方法
System.out.println("data:"+o);
}
//2.2 使用有参返回指定类型数组
//无参不好的一点就是返回的是 Object[] 类型,操作比较麻烦.想要拿到长度,Object是拿不到长度的
String[] strings = stream.toArray(String[]::new);
for(String str : strings){
System.out.println("data:"+str + ",length:"+str.length());
}
}
}
3.Stream流中数据聚合/分组/分区/拼接操作
/**
* TODO Student实体类
*
* @author liuzebiao
* @Date 2020-1-10 13:38
*/
public class Student {
private String name;
private int age;
private int score;
public Student(String name, int age, int score) {
this.name = name;
this.age = age;
this.score = score;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public int getScore() {
return score;
}
public void setScore(int score) {
this.score = score;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
", score=" + score +
'}';
}
}
1.聚合操作
//最大值
Collectors.maxBy();
//最小值
Collectors.minBy();
//总和
Collectors.summingInt();/Collectors.summingDouble();/Collectors.summingLong();
//平均值
Collectors.averagingInt();/Collectors.averagingDouble();/Collectors.averagingLong();
//总个数
Collectors.counting();
/**
* Stream流数据--聚合操作
* 备注:切记Stream流只能被消费一次,流就失效了
* 如下只是示例代码
* @author liuzebiao
* @Date 2020-1-10 13:37
*/
public class CollectDataToArray{
public static void main(String[] args) {
Stream<Student> studentStream = Stream.of(
new Student("赵丽颖", 58, 95),
new Student("杨颖", 56, 88),
new Student("迪丽热巴", 56, 99),
new Student("柳岩", 52, 77)
);
//聚合操作
//获取最大值(Stream流 max()方法亦可)
//max()方法实现
//Optional<Student> max = studentStream.max((s1, s2) -> s1.getScore() - s2.getScore());
//(聚合)实现
Optional<Student> max = studentStream.collect(Collectors.maxBy((s1, s2) -> s1.getScore() - s2.getScore()));
System.out.println("最大值:"+max.get());
//获取最小值(Stream流 min()方法亦可)
//min()方法实现
//Optional<Student> min = studentStream.max((s1, s2) -> s2.getScore() - s1.getScore());
//(聚合)实现
Optional<Student> min = studentStream.collect(Collectors.minBy((s1, s2) -> s1.getScore() - s2.getScore()));
System.out.println("最小值:"+min.get());
//求总和(使用Stream流的map()和reduce()方法亦可求和)
//map()和reduce()方法实现
//Integer reduce = studentStream.map(s -> s.getAge()).reduce(0, Integer::sum);
//(聚合)简化前
//Integer ageSum = studentStream.collect(Collectors.summingInt(s->s.getAge()));
//(聚合)使用方法引用简化
Integer ageSum = studentStream.collect(Collectors.summingInt(Student::getAge));
System.out.println("年龄总和:"+ageSum);
//求平均值
//(聚合)简化前
//Double avgScore = studentStream.collect(Collectors.averagingInt(s->s.getScore()));
//(聚合)使用方法引用简化
Double avgScore = studentStream.collect(Collectors.averagingInt(Student::getScore));
System.out.println("分数平均值:"+avgScore);
//统计数量(Stream流 count()方法亦可)
//count()方法实现
//long count = studentStream.count();
//(聚合)统计数量
Long count = studentStream.collect(Collectors.counting());
System.out.println("数量为:"+count);
}
}
最大值:Student{name='迪丽热巴', age=56, score=99}
最小值:Student{name='柳岩', age=52, score=77}
年龄总和:222
分数平均值:89.75
数量为:4
2.分组操作
/**
* Stream流数据--分组操作
* 备注:切记Stream流只能被消费一次,流就失效了
* 如下只是示例代码
* @author liuzebiao
* @Date 2020-1-10 13:37
*/
public class CollectDataToArray{
public static void main(String[] args) {
Stream<Student> studentStream = Stream.of(
new Student("赵丽颖", 52, 56),
new Student("杨颖", 56, 88),
new Student("迪丽热巴", 56, 99),
new Student("柳岩", 52, 53)
);
//1.按照具体年龄分组
Map<Integer, List<Student>> map = studentStream.collect(Collectors.groupingBy((s -> s.getAge())));
map.forEach((key,value)->{
System.out.println(key + "---->"+value);
});
//2.按照分数>=60 分为"及格"一组 <60 分为"不及格"一组
Map<String, List<Student>> map = studentStream.collect(Collectors.groupingBy(s -> {
if (s.getScore() >= 60) {
return "及格";
} else {
return "不及格";
}
}));
map.forEach((key,value)->{
System.out.println(key + "---->"+value);
});
}
}
3.多级分组操作
/**
* Stream流数据--多级分组操作
* 备注:切记Stream流只能被消费一次,流就失效了
* 如下只是示例代码
* @author liuzebiao
* @Date 2020-1-10 13:37
*/
public class CollectDataToArray{
public static void main(String[] args) {
Stream<Student> studentStream = Stream.of(
new Student("赵丽颖", 52, 95),
new Student("杨颖", 56, 88),
new Student("迪丽热巴", 56, 55),
new Student("柳岩", 52, 33)
);
//多级分组
//1.先根据年龄分组,然后再根据成绩分组
//分析:第一个Collectors.groupingBy() 使用的是(年龄+成绩)两个维度分组,所以使用两个参数 groupingBy()方法
// 第二个Collectors.groupingBy() 就是用成绩分组,使用一个参数 groupingBy() 方法
Map<Integer, Map<Integer, Map<String, List<Student>>>> map = studentStream.collect(Collectors.groupingBy(str -> str.getAge(), Collectors.groupingBy(str -> str.getScore(), Collectors.groupingBy((student) -> {
if (student.getScore() >= 60) {
return "及格";
} else {
return "不及格";
}
}))));
map.forEach((key,value)->{
System.out.println("分数:" + key);
value.forEach((k2,v2)->{
System.out.println("\t" + v2);
});
});
}
}
4.分区操作
//1.一个参数
partitioningBy(Predicate<? super T> predicate)
//2.两个参数(多级分区)
partitioningBy(Predicate<? super T> predicate, Collector<? super T, A, D> downstream)
/**
* Stream流数据--多级分组操作
* 备注:切记Stream流只能被消费一次,流就失效了
* 如下只是示例代码
* @author liuzebiao
* @Date 2020-1-10 13:37
*/
public class CollectDataToArray{
public static void main(String[] args) {
Stream<Student> studentStream = Stream.of(
new Student("赵丽颖", 52, 95),
new Student("杨颖", 56, 88),
new Student("迪丽热巴", 56, 55),
new Student("柳岩", 52, 33)
);
//分区操作
Map<Boolean, List<Student>> partitionMap = studentStream.collect(Collectors.partitioningBy(s -> s.getScore() > 60));
partitionMap.forEach((key,value)->{
System.out.println(key + "---->" + value);
});
}
}
false---->[Student{name='迪丽热巴', age=56, score=55}, Student{name='柳岩', age=52, score=33}]
true---->[Student{name='赵丽颖', age=52, score=95}, Student{name='杨颖', age=56, score=88}]
5.拼接操作
//无参数--等价于 joining("");
joining()
//一个参数
joining(CharSequence delimiter)
//三个参数(前缀+后缀)
joining(CharSequence delimiter, CharSequence prefix,CharSequence suffix)
/**
* Stream流数据--多级分组操作
* 备注:切记Stream流只能被消费一次,流就失效了
* 如下只是示例代码
* @author liuzebiao
* @Date 2020-1-10 13:37
*/
public class CollectDataToArray{
public static void main(String[] args) {
Stream<Student> studentStream = Stream.of(
new Student("赵丽颖", 52, 95),
new Student("杨颖", 56, 88),
new Student("迪丽热巴", 56, 55),
new Student("柳岩", 52, 33)
);
//拼接操作
//无参:join()
String joinStr1 = studentStream.map(s -> s.getName()).collect(Collectors.joining());
System.out.println(joinStr1);
//一个参数:joining(CharSequence delimiter)
String joinStr2 = studentStream.map(s -> s.getName()).collect(Collectors.joining(","));
System.out.println(joinStr2);
//三个参数:joining(CharSequence delimiter, CharSequence prefix,CharSequence suffix)
String joinStr3 = studentStream.map(s -> s.getName()).collect(Collectors.joining("—","^_^",">_<"));
System.out.println(joinStr3);
}
}
赵丽颖杨颖迪丽热巴柳岩
赵丽颖,杨颖,迪丽热巴,柳岩
^_^赵丽颖—杨颖—迪丽热巴—柳岩>_<
1.forEach
迭代集合中元素。接收一个 Lambda 表达式
然后在 Stream 的每一个元素上执行该表达式
此操作是及早求值方法
public static void main(String[] args){
List<String> list = new ArrayList<>();
Collections.addAll(list,"Mary","Lucy","James","Johson","Steve");
//forEach()遍历
//未简写
//list.forEach((String str)->{
// System.out.println(str);
//});
//简写1
//list.forEach(str-> System.out.println(str));
//最终简写
list.forEach(System.out::println);
}
2.count
public class StreamDemo {
public static void main(String[] args){
List<String> list = new ArrayList<>();
Collections.addAll(list,"Mary","Lucy","James","Johson","Steve");
//count()计算集合中元素个数
long count = list.stream().count();
System.out.println("元素个数为:"+count);
}
}
3.filter
public class StreamDemo {
public static void main(String[] args){
List<String> list = new ArrayList<>();
Collections.addAll(list,"Mary","Lucy","James","Johson","Steve");
//filter()过滤,返回以"J"开头的名字
list.stream().filter(str->str.startsWith("J")).forEach(System.out::println);
//使用BiPredicate,就搞复杂了,如下,没啥意思(只会让人看不懂,哈哈)
//BiPredicate<String,String> consumer = String::startsWith;
//list.stream().filter(str->consumer.test(str,"J")).forEach(System.out::println);
}
}
4.limit
public class StreamDemo {
public static void main(String[] args){
List<String> list = new ArrayList<>();
Collections.addAll(list,"Mary","Lucy","James","Johson","Steve");
//limit()截取,截取list集合前三个元素
list.stream().limit(3).forEach(System.out::println);
}
}
5.skip
public class StreamDemo {
public static void main(String[] args){
List<String> list = new ArrayList<>();
Collections.addAll(list,"Mary","Lucy","James","Johson","Steve");
//skip()跳过list集合前2个元素,获取剩下的元素
list.stream().skip(2).forEach(System.out::println);
}
}
6.map
map() 方法,可以将流中的元素映射到另一个流中。即:可以将一种类型的流转换为另一种类型的流(区别:map返回的是指定类型(比如int),而flatMap返回的还是一个Stream流),map() 方法是一个非终结方法。该接口需要一个 Function 函数式接口参数,可以将当前流中的T类型数据转换为另一种R类型的流。
这个方法有三个对于原始类型的变种方法,分别是:mapToInt,mapToLong 和 mapToDouble。这三个方法也比较好理解,比如 mapToInt 就是把原始 Stream 转换成一个新的 Stream,这个新生成的 Stream 中的元素都是 int 类型。之所以会有这样三个变种方法,可以免除自动装箱/拆箱的额外消耗
public class StreamDemo {
public static void main(String[] args){
List<String> list = new ArrayList<>();
Collections.addAll(list,"11","22","33","44","55");
//通过map()方法,可以将String类型的流转换为int类型的流
/*list.stream().map((String str)->{
return Integer.parseInt(str);
}).forEach((Integer num) -> {
System.out.println(num);
});*/
//简化:
//list.stream().map(str->Integer.parseInt(str)).forEach(str->System.out.println(str));
//简化后:
list.stream().map(Integer::parseInt).forEach(System.out::println);
}
}
7.flatMap
flatMap 的使用,同 map 类似。map只是一维 1对1 的映射,返回的是指定的类型;
而flatMap返回的则还是一个Stream流,可以对其进行进一步操作。(区别:map返回的是指定类型(比如int),而flatMap返回的还是一个Stream流)
我的理解为:假如你的集合流中包含子集合(或者需要更深进一步操作),那么使用 flatMap 可以返回该子集合的集合流
public class Province {
private String name;
private List<String> city;
//get/set 方法
}
public class flatMapDemo{
public static void main(String[] args) {
List<Province> provinceList = new ArrayList<>();
List<String> bjCityList = new ArrayList<>();
bjCityList.add("海淀");
bjCityList.add("朝阳");
List<String> shCityList = new ArrayList<>();
shCityList.add("黄埔");
shCityList.add("闵行");
Province bjProvince = new Province();
bjProvince.setName("北京");
bjProvince.setCity(bjCityList);
provinceList.add(bjProvince);
Province shProvince = new Province();
shProvince.setName("上海");
shProvince.setCity(shCityList);
provinceList.add(shProvince);
//使用map,需要多次forEach
provinceList.stream().map(str->str.getCity()).forEach(cityList -> cityList.forEach(System.out::println));
System.out.println("----------");
//使用 flatMap,一次forEach即可
provinceList.stream().flatMap(str->str.getCity().stream()).forEach(System.out::println);
}
}
8.sorted
//根据元素的自然规律排序
Stream<T> sorted();
//根据比较器指定的规则排序
Stream<T> sorted(Comparator<? super T> comparator);
因为 sorted() 方法是一个非终结方法,所以必须调用终止方法。
场景:①sorted() 方法:按照自然规律,默认为升序排序。
②sorted(Comparator comparator) 方法,按照指定的比较器规则排序(以降序为例)。 示例如下:
public class StreamDemo {
public static void main(String[] args){
//sorted():根据元素的自然规律排序
Stream<Integer> stream01 = Stream.of(66,33,11,55);
stream01 .sorted().forEach(System.out::println);
//sorted(Comparator<? super T> comparator):根据比较器规则降序排序
Stream<Integer> stream02 = Stream.of(66,33,11,55);
stream02 .sorted((i1,i2)-> i2-i1).forEach(System.out::println);
}
}
9.distinct
Stream<T> distinct();
distinct() 方法,可以用来去除重复数据。因为 distinct() 方法是一个非终结方法,所以必须调用终止方法。
去除重复数据,此处有几种情况:①基本类型去重 ②String类型去重 ③引用类型去重(对象去重)
①②使用 distinct() 方法可以直接去重 ③对象类型需要重写 equals() 和 hasCode() 方法,使用 distinct() 方法才能去重成功
public class StreamDemo {
public static void main(String[] args){
//基本类型去重
Stream<Integer> stream01 = Stream.of(66,33,11,55,33,22,55,66);
stream01 .distinct().forEach(System.out::println);
//字符串去重
Stream<String> stream02 = Stream.of("AA","BB","AA");
stream02.distinct().forEach(System.out::println);
//自定义对象去重
//(Person对象类,有String name,Integer age 两个属性,两参数构造器,get/set()方法,重写了equals(),hashCode(),toString()方法。此处就不附Person实体类了)
BiFunction<String,Integer,Person> fn1 = Person::new;
Stream<Person> stream14 = Stream.of(fn1.apply("西施", 18), fn1.apply("貂蝉", 20), fn1.apply("王昭君", 22), fn1.apply("杨玉环", 23), fn1.apply("杨玉环", 23));
stream14.distinct().forEach(System.out::println);
}
}
10.match
//allMatch 全匹配(匹配所有,所有元素都需要满足条件-->返回true)
boolean allMatch(Predicate<? super T> predicate);
//anyMatch 匹配某个元素(只要有一个元素满足条件即可-->返回true)
boolean anyMatch(Predicate<? super T> predicate);
//noneMatch 匹配所有元素(所有元素都不满足指定条件-->返回true)
boolean noneMatch(Predicate<? super T> predicate);
public class StreamDemo {
public static void main(String[] args){
Stream<Integer> stream01 = Stream.of(5, 3, 6, 1);
boolean allMatch = stream01.allMatch(i -> i > 0);
System.out.println("allMatch匹配:"+allMatch);
Stream<Integer> stream02 = Stream.of(5, 3, 6, 1);
boolean anyMatch = stream02 .anyMatch(i -> i > 5);
System.out.println("anyMatch匹配:"+anyMatch);
Stream<Integer> stream03 = Stream.of(5, 3, 6, 1);
boolean noneMatch = stream03 .noneMatch(i -> i < 3);
System.out.println("noneMatch匹配:"+noneMatch);
}
}
allMatch匹配:true
anyMatch匹配:true
noneMatch匹配:false
11.max / min
max() 和 min() 方法,用来获取 Stream 流中的最大值和最小值
public class StreamDemo {
public static void main(String[] args){
//max()
Stream<Integer> stream01 = Stream.of(33, 11, 22, 5);
Optional<Integer> max = stream01.max((i1, i2) -> i1 - i2);
System.out.println("最大值:"+max.get());
//min()
Stream<Integer> stream02 = Stream.of(33, 11, 22, 5);
Optional<Integer> min = stream02.min((i1, i2) -> i1 - i2);
System.out.println("最小值:"+min.get());
}
}
12.reduce
求和流程:
第一次:将默认值赋值给x,取出集合第一个元素赋值给y
第二步:将上一次返回的结果赋值给x,取出集合第二个元素赋值给y
第三步:继续执行第二步(如下图所示)
public class StreamDemo {
public static void main(String[] args){
//reduce():求和操作
Stream<Integer> stream01 = Stream.of(4,3,5,6);
Integer sum = stream01.reduce(0,(x,y)-> x + y);
System.out.println("求和:"+sum);
//reduce():求最大值操作
Stream<Integer> stream01 = Stream.of(4,3,5,6);
Integer sum = stream01.reduce(0,(x,y)-> x + y);
System.out.println("最大值为:"+sum);
}
}
13.map 和 reduce 方法组合使用
public class StreamDemo {
/**
* map() 和 reduce() 方法组合使用
*/
public static void main(String[] args){
BiFunction<String,Integer,Person> fn2 = Person::new;
//1.求出所有年龄的总和(年龄为int值,默认为0,此处可以使用待默认值的reduce()方法)
Stream<Person> stream01 = Stream.of(fn2.apply("刘德华", 58), fn2.apply("张学友", 56), fn2.apply("郭富城", 54), fn2.apply("黎明", 52));
//基本写法:
//Integer total = stream01.map(p -> p.getAge()).reduce(0,(x, y) -> x + y);
//(方法引用)简化后:
Integer total = stream01.map(p -> p.getAge()).reduce(0,Integer::sum);
System.out.println("年龄总和为:"+total);
//2.找出最大年龄
Stream<Person> stream02 = Stream.of(fn2.apply("刘德华", 58), fn2.apply("张学友", 56), fn2.apply("郭富城", 54), fn2.apply("黎明", 52));
//基本写法:
//Integer maxAge = stream02.map(p -> p.getAge()).reduce(0, (x, y) -> x > y ? x : y);
//(方法引用)简化后:
Integer maxAge = stream02.map(p -> p.getAge()).reduce(0, Integer::max);
System.out.println("最大年龄为:"+maxAge);
}
}
14.mapToInt / mapToDouble / mapToLong
//mapToInt()
IntStream mapToInt(ToIntFunction<? super T> mapper);
//mapToLong()
LongStream mapToLong(ToLongFunction<? super T> mapper);
//mapToDouble()
DoubleStream mapToDouble(ToDoubleFunction<? super T> mapper);
//1.Integer是一个类,占用的内存肯定比 int 大
//2.Stream流在操作时,会存在自动装箱和拆箱操作
Stream<Integer> stream = Stream.of(2,3,5,6,7);
//把大于3的打印出来(num在Stream流中是Integer类型,在与3比较时,显然会存在自动拆装箱问题),效率会有影响
stream.filter(num -> num > 3).forEach(System.out::println);
所以在 JDK8 中,对 Stream 流还新增了一个 mapToInt()方法。
该方法可以将流中操作的 Integer 包装类,在 Stream 流中转换成直接来操作 int 类型,
效率明显会高一点。
IntStream mapToInt(ToIntFunction<? super T> mapper);
//此时 mapToInt() 接收一个 ToIntFunction 的函数式接口
//接下来我们来分析参数:ToIntFunction<? super T> mapper
@FunctionalInterface
public interface ToIntFunction<T> {
/**
* Applies this function to the given argument.
*
* @param value the function argument
* @return the function result
*/
int applyAsInt(T value);
}
//我们发现:ToIntFunction 是一个函数式接口,里面仅有一个抽象方法 applyAsInt(),
//applyAsInt() 方法:接收一个参数 ,返回一个int型。接下来我们便知道如何使用Lambda表达式来使用 mapToInt() 方法了
public class StreamDemo {
public static void main(String[] args){
//使用 mapToInt()方法
IntStream intStream = Stream.of(1, 2, 3, 4, 5, 6).mapToInt((Integer num) -> {
return num.intValue();
});
//(使用方法引用)简化后
IntStream intStream1 = Stream.of(1, 2, 3, 4, 5, 6).mapToInt(Integer::intValue);
intStream1.filter(n->n>3).forEach(System.out::println);
/**
* 使用mapToInt(),返回值是一个IntStream类型.我们看一下它们的继承结构图(如下所示):
* IntStream 和 Stream<Integer> 类型进行比较。发现他们都继承自 BaseStream。所以它们区别不大
* 只不过 IntStream 内部操作的是 int 基本类型的数据,省去自动拆装箱过程。从而可以节省内存开销
*/
}
}
15.concat
public static <T> Stream<T> concat(Stream<? extends T> a, Stream<? extends T> b) {
Objects.requireNonNull(a);
Objects.requireNonNull(b);
@SuppressWarnings("unchecked")
Spliterator<T> split = new Streams.ConcatSpliterator.OfRef<>(
(Spliterator<T>) a.spliterator(), (Spliterator<T>) b.spliterator());
Stream<T> stream = StreamSupport.stream(split, a.isParallel() || b.isParallel());
return stream.onClose(Streams.composedClose(a, b));
}
concat() 方法,可以将两个Stream流合并成一个流进行返回。如果是三个流,则需要两两合并,不能一次性合并三个流。concat() 方法是 Stream 接口的静态方法,我们可以直接使用【类名.方法名】调用。
public class StreamDemo {
public static void main(String[] args){
//concat()方法
Stream<Integer> aStream = Stream.of(1, 2, 3);
Stream<Integer> bStream = Stream.of(4, 5, 6);
Stream<Integer> concatStream = Stream.concat(aStream, bStream);
concatStream.forEach(System.out::println);
}
}
16.allMatch / anyMatch / noneMatch 匹配相关
allMatch:是不是Stream中的所有元素都满足给定的匹配条件
anyMatch:Stream中是否存在任何一个元素满足匹配条件
noneMatch:是不是Stream中的所有元素都不满足给定的匹配条件
// Stream中元素,所有元素长度都>1
boolean flag = Stream.of("one", "two", "three", "four").allMatch(str -> str.length() > 1);
System.out.println(flag); // 返回值:true
// Stream中元素,是不是存在任何一个元素长度>1
boolean flag1 = Stream.of("one", "two", "three", "four").anyMatch(str -> str.length() > 4);
System.out.println(flag1); // 返回值:true
// Stream中元素,所有元素长度是不是都 <10
boolean flag2 = Stream.of("one", "two", "three", "four").noneMatch(str -> str.length() < 1);
System.out.println(flag2); // 返回值:true
六.并行stream的使用
6.1 并行 Stream 流的获取方式(两种)
/**
* 并行 Stream 流的获取方式
*/
public class ParallelStream {
public static void main(String[] args) {
//方式一:通过.parallelStream()方法,直接获取并行Stream流
List<String> list = new ArrayList<>();
Stream<String> stream = list.parallelStream();
//方式二:通过.parallel()方法,将串行流转成并行流
Stream<String> parallelStream = list.stream().parallel();
//并行Stream流测试
Stream<Integer> stream = Stream.of(4, 5, 6, 3, 21, 56).parallel();
stream.filter(num->{
System.out.println(Thread.currentThread().getName() + "--" + num);
return num>3;
}).count();
}
}
ForkJoinPool.commonPool-worker-5--6
ForkJoinPool.commonPool-worker-3--4
main--3
ForkJoinPool.commonPool-worker-4--21
ForkJoinPool.commonPool-worker-1--5
ForkJoinPool.commonPool-worker-2--56
6.2.判断 Stream 流是串行流还是并行流
//我们可以通过isParallel()方法来判断
public final boolean isParallel() {
return sourceStage.parallel;
}
/**
* 判断当前流是串行流、并行流
*/
public class isParallelDemo{
public static void main(String[] args) {
Stream<Integer> stream01 = Stream.of(4, 5, 6, 3, 21, 56);
System.out.println(stream01); //false
Stream<Integer> stream02 = Stream.of(4, 5, 6, 3, 21, 56).parallel();
System.out.println(stream02); //true
}
}
6.3 串行流、并行流、for循环 求和效率对比
/**
* TODO for循环、串行Stream流、并行Stream流 对5亿个数字进行求和
*
* @author liuzebiao
* @Date 2020-1-10 17:17
*/
public class EffectiveDemo {
private int times = 500000000;
long start;
long end;
@Before
public void init(){
start = System.currentTimeMillis();
}
@After
public void destory(){
end = System.currentTimeMillis();
System.out.println("消耗时间:"+(end-start));
}
//for循环(求和5亿)
@Test
public void testFor(){
int sum = 0;
for (int i = 0; i < times; i++) {
sum += i;
}
System.out.println(sum);
}
//串行Stream流(求和5亿)
@Test
public void testStream(){
//5亿数字太大,所以此处使用 LongStream
//range():需要传入开始节点和结束节点两个参数,返回的是一个有序的LongStream.包含开始节点和结束节点两个参数之间所有的参数,间隔为1.
//rangeClosed():功能和range()类似.差别就是rangeClosed包含最后的结束节点,range()不包含。
LongStream.rangeClosed(0, times).reduce(0, Long::sum);
}
//并行Stream流(求和5亿)
@Test
public void testParallelStream(){
LongStream.rangeClosed(0, times).parallel().reduce(0, Long::sum);
}
}
(for循环)消耗时间:130
(串行Stream流)消耗时间:649
(并行Stream流)消耗时间:85
6.4 并行 Stream 流 线程不安全问题
不安全范例:
public class ParallelUnSafeDemo{
/**
* 并行Stream流 线程不安全 案例
*/
@Test
public void parallelStreamNotice() throws InterruptedException {
ArrayList<Integer> list = new ArrayList<>();
IntStream.rangeClosed(1, 1000).parallel().forEach(i -> list.add(i));
System.out.println("list="+list.size());
}
}
list=865
解决方法:
针对如上情况,为什么会出现这种情况,我们先来分析一下。共总结出如下三种解决方案:
1.①我们可以使用万能的 synchronized 同步代码块来解决线程安全性问题;②也可以使用 Collections 集合工具类,使用 synchronizedList() 方法,当我们传入一个线程不安全的 list 后,会给我们返回一个线程安全的 list ,然后我们便可以对线程安全的 list 进行操作,类似于 synchronized 同步代码块;
2.我们分析发现 ArrayList 集合本身就是线程不安全的,所以我们可以使用线程安全的集合,比如:使用Vector来替换 ArrayList
3.我们也可以调用 Stream 流的 collect()/toArray() 收集方法,它也会将集合变成线程安全的(此处会使用.boxed() 方法)
/**
* 解决 parallelStream 线程安全问题
*/
public class StreamSafeResolve {
@Test
public void parallelStreamNotice() throws InterruptedException {
//方案一:1.使用同步代码块
Object obj = new Object();
ArrayList<Integer> list = new ArrayList<>();
IntStream.rangeClosed(1, 1000).parallel().forEach((str)->{
synchronized (obj){
list.add(str);
}
}
);
System.out.println("list="+list.size());
//2.使用 Collections 集合工具类,有一个 synchronizedList() 方法,传入一个不安全的list,会返回一个线程安全的list。然后对线程安全的list进行操作
ArrayList<Integer> lists = new ArrayList<>();
List<Integer> list = Collections.synchronizedList(lists);
IntStream.rangeClosed(1, 1000).parallel().forEach((str)->{ list.add(str); });
System.out.println("list="+list.size());
//方案二:使用线程安全的集合类
//Vector是线程安全的集合
Vector<Integer> vector = new Vector<>();
IntStream.rangeClosed(1, 1000).parallel().forEach((str)->{ vector.add(str); });
System.out.println("vector="+vector.size());
//方案三:调用 Stream 流的 collect()/toArray()收集方法。它也会变成线程安全的
List<Integer> list = IntStream.rangeClosed(1, 1000).boxed().collect(Collectors.toList());
System.out.println("list="+list.size());
}
}
6.5 并行 Stream 流底层原理分析(Fork/Join 框架)
Fork/Join 框架主要包含三个模块:
线程池
:ForkJoinPool任务对象
:ForkJoinTask
执行任务的线程
:ForkJoinWorkerThread
6.5.2 原理介绍
1.分治法
ForkJoinPool 主要使用分治法(Divide-and-Conquer Algorithm)来解决问题。根据名称我们可以将其分为 Fork 和 Join 两个阶段。典型的应用比如快速排序算法。
ForkJoinPool需要使用相对少的线程来处理大量的任务。比如要对 1000万个数据进行排序,那么会将这个任务分隔成两个 500万 的排序任务 和 一个针对这两组 500万 数据的合并任务。以此类推,对于 500万 的数据也会做出同样的分隔处理,到最后会设置一个阈值来规定当数据规模到多少时,停止这样的分割处理操作。比如,当元素的数量小于 10 时,会停止分隔,转而使用插入排序对他们进行排序。那么到最后,所有的任务加起来有大概2000000+ 个。问题的关键自安于,对于一个任务而言,只有当它所有的子任务完成之后,它才能够被执行。
2.工作窃取算法
Fork/Join 最核心的地方,就是利用了现代硬件设备多核。当一个操作完成的时候会有空闲的CPU,那么如何利用好这个空闲的CPU就成了提高性能的关键,而我们这里提到的工作窃取(work-stealing)算法就是整个 Fork/Join 框架的核心理念。Fork/Join 工作窃取算法,是指某个线程从其他队列里窃取任务来执行。
那么为什么需要使用工作窃取算法呢?假如我们需要做一个比较大的任务,我们可以把这个任务分割为若干互不依赖的子任务,为了减少线程间的竟争,于是把这些子任务分别放到不同的队列里,并为每个队列创建一个单独的线程来执行队列里的任务,线程和队列一一对应。比如 A 线程负责处理 A 队列里的任务。但是有的线程会先把自己队列里的任务干完,而其他线程对应的队列里还有任务等待处理。干完活的线程与其等着看,不如去帮其他线程干活,于是它就去其他线程的队列里窃取一个任务来执行。而在这时它们会访问同一个队列,所以为了减少窃取任务线程和被窃取任务线程之间的竞争,通常会使用双端队列,被窃取任务线程永远从双端队列的头部拿任务执行,而窃取任务的线程永远从双端队列的尾部拿任务执行。
工作窃取算法的优点是充分利用线程进行并行计算,并减少了线程间的竞争,其缺点是①在某些情况下还是存在竞争,比如双端队列里只有一个任务时。②消耗了更多的系统资源,比如创建多个线程和多个双端队列。
上文中已经提到了Java8 引入了自动并行化的概念。它能够让一部分 Java 代码自动地以并行的方式执行,也就是我们使用了 ForkjoinPool 的 ParallelStream。
对于 ForkJoinPool 通用线程池的线程数量,通常使用默认值就可以了,即运行时计算机的处理器数量。可以通过设置系统属性:java.util.concurrent.ForkJoinPool.common.parallelism = N(N 为线程数量),来调整 ForkJoinPool 的线程数量,可以尝试调整成不同的参数来观察每次的输出结果。
6.5.3 Fork/Join 案例Demo
需求:使用 Fork/Join 计算 1-10000的和,当一个任务的计算数量大于3000时拆分任务,数量小于3000时计算。
1.普通求和
public class ForkJoinDemo {
public static void main(String[] args) {
//开始时间
Long start = System.currentTimeMillis();
long sum = 0l;
for (long i = 1; i <= 9999999999L; i++) {
sum+=i;
}
System.out.println(sum);
//结束时间
Long end = System.currentTimeMillis();
System.out.println("消耗时间:"+(end-start));
}
}
2.Fork/Join求和
/**
* TODO Fork/Join 求和Demo
* @author liuzebiao
* @Date 2020-1-14 17:03
*/
public class ForkJoinDemo {
public static void main(String[] args) {
Long start = System.currentTimeMillis();
//放入线程池
ForkJoinPool pool = new ForkJoinPool();
SumRecursiveTask task = new SumRecursiveTask(1, 59999999999L);
Long result = pool.invoke(task);
System.out.println("result="+result);
Long end = System.currentTimeMillis();
System.out.println("消耗时间:"+(end-start));
}
}
//1.创建一个求和的任务
//RecursiveTask:表示一个任务
class SumRecursiveTask extends RecursiveTask<Long>{
//大于3000要拆分(创建一个变量)
//是否要拆分的临界值
private static final long THRESHOLD = 3000L;
//起始值
private final long start;
//结束值
private final long end;
//构造方法(传递起始值、结束值)
public SumRecursiveTask(long start, long end) {
this.start = start;
this.end = end;
}
//任务编写完成
@Override
protected Long compute() {
long length = end - start;
//计算
if(length < THRESHOLD){
long sum = 0;
for (long i = start; i <= end; i++) {
sum +=i;
}
return sum;
}else{
//拆分
long middle = (start + end) /2;
SumRecursiveTask left = new SumRecursiveTask(start,middle);
left.fork();
SumRecursiveTask right = new SumRecursiveTask(middle+1,end);
right.fork();
return left.join() +right.join();
}
}
}
6.6 并行 Stream 流总结
parallel 并行 Stream 流是线程不安全的;
parallel 并行 Stream 流使用的场景是 CPU 密集型的,只是做到别浪费 CPU,假如本身电脑的 CPU 的负载很大,那还到处用并行流,那并不能起到作用;
I/O 密集型、磁盘I/O、网络I/O 都属于 I/O 操作,这部分操作时较少消耗 CPU 资源,一般并行流中不适用于 I/O密集型的操作,就比如使用并行流进行大批量的消息推送,涉及到了大量 I/O,使用并行流反而慢了很多;
在使用并行流的时候,是无法保证元素的顺序的,也就是即使你使用了同步集合也只能保证元素都正确,但无法保证其中的顺序。
注:以上资料参考 来源:Java8 Stream:2万字20个实例,玩转集合的筛选、归约、分组、聚合_云深不知处-CSDN博客_java stream 聚合