1. Stream流的出现原因
我们操作集合的时候有时候十分麻烦,如下
上面的集合操作数据的时候,每一个需求都需要一个新的集合来存放数据,十分麻烦。
为此我们引入Stream流式操作。
注意:Stream和IO流没有任何关系。
2. Stream流式思想概述
Stream流式思想类似与工厂车间的“生产流水线”。Stream流不是一种数据结构,不保存数据,而是对数据进行加工处理,Stream可以看作式流水线上的一个工序,在流水线上,通过多个工序让一个原材料加工成一个产品。
看到Stream的filter和map方法参数,都是一个接口,可以使用lambda表达式。
使用方式如下:
public class Demo01Intro {
public static void main(String[] args) {
List list = new ArrayList<>();
Collections.addAll(list, "张无忌", "周芷若", "赵敏", "张强", "张三丰");
list.stream().filter((s) -> {
System.out.println("s1" + s);
return s.startsWith("张"); // 返回姓张的
}).filter((s)->{
System.out.println("s2" +s);
return s.length() == 3; // 返回长3的
}).forEach((s)->{
System.out.println(s);
});
}
且我们运行结果这样的:
3. 获取流的两种方式
- 根据Collection获取Stream
- Stream中的静态方法of获取流
public class Demo02GetStream {
public static void main(String[] args){
// 1. 根据Collection获取流
// list继承Collection
List list = new ArrayList<>();
Stream stream1 = list.stream();
// Set获取流
HashSet set = new HashSet<>();
Stream stream2 = set.stream();
// 根据Map的key和value获取流
Map map = new HashMap<>();
Stream stream3 = map.keySet().stream();
Stream stream4 = map.values().stream();
Stream> stream5 = map.entrySet().stream();
// 2. 根据Stream的of静态方法
// public static Stream of(T... values)
Stream stream6 = Stream.of("a", "b", "c");
String[] strs = {"aa","bb","cc"};
Stream stream7 = Stream.of(strs);
// 基本数据类型的数据的Stream流会把整个数组 int[] 作为一个值传进流中,
// 而不能操作数组里面的数据
int[] arr = {1,2,3};
Stream arr1 = Stream.of(arr);
}
}
4. Stream的注意事项
1. Stream只能操作一次
2. Stream方法返回的是新的流
3. Stream方法不调用终结方法,中间的操作不会执行
5. Stream的常用方法
- 终结方法:返回值类型不再是Stream类型的方法,不在支持链式调用。包括count,forEach方法
- 非终结方法(函数拼接方法):返回值类型仍然式Stream类型的方法,支持链式调用。
5.1 forEach
遍历留里面的所有元素进行操作
5.2 count
统计流里面元素的个数
long count = list.stream().count();
System.out.println(count);
5.3 filter
用户过滤数据,返回符合过滤条件的数据
list.stream().filter((String str) -> {
return str.length() == 3;
}).forEach(s -> System.out.println(s));
5.4 limit
对流进行截取,只取前n个
list.stream().limit(3).forEach(s -> System.out.println(s));
5.5 skip
和limit相反,对流进行截取,跳过前n个获取后面的
list.stream().skip(3).forEach(s -> System.out.println(s));
5.6 map
将流中的元素映射到另一个流中去。将一种类型的流转换成另一种类型的流
// String类型的流变成Integer类型
Stream original = Stream.of("1", "2", "3");
Stream integerStream = original.map(s -> {
return Integer.parseInt(s);
});
integerStream.forEach(s->{
System.out.println(s);
});
5.7 sorted
可以对数据进行排序,是一个函数拼接方法。
// sorted排序
original.map(s -> {
return Integer.parseInt(s);
}).sorted().forEach(System.out::println);
original.map(s -> {
return Integer.parseInt(s);
}).sorted((Integer i1, Integer i2)->{
return i2 - i1;
}).forEach(System.out::println);
5.8 distinct
如果要去除重复元素,可以使用distinct方法。是一个函数拼接方法。
Stream stream = Stream.of(1, 2, 5, 4, 3, 4, 3, 5, 2);
stream.distinct().forEach(System.out::println);
注意对于自定义类型的数据,无法直接去重,需要我们重写equals和hashcode方法
5.9 match
如果要判断数据是否匹配指定的条件,可以使用match方法。是一个终结方法。
一共有三个
boolean anyMatch(Predicate super T> predicate);
boolean allMatch(Predicate super T> predicate);
boolean noneMatch(Predicate super T> predicate);
5.10 find
如果需要寻找某些数据,可以使用find方法
Optional findFirst();
Optional findAny();
Optional first = stream.findFirst();
Integer integer = first.get();
System.out.println(integer);
5.11 max和min
获取最大和最小值
Optional max = stream.max((o1, o2) -> o1 - o2);
System.out.println("最大值" + max.get());
Optional min = stream.min((o1, o2) -> o1 - o2);
System.out.println("最小值" + min.get());
5.12 reduce
如果需要将所有数据归纳得到一个数据,可以使用reduce方法。
T reduce(T identity, BinaryOperator accumulator);
T identity:默认值
BinaryOperator accumulator:对数据进行处理的方式
Integer reduce = stream.reduce(0, (x, y) -> {
System.out.println("x = " + x +", y = " + y);
return x + y;
});
System.out.println(reduce);
// 获取最大值
Integer reduce = stream.reduce(0, (x, y) -> {
return x > y ? x : y;
});
System.out.println(reduce);
5.13 map和reduce的组合使用
通过map的转换,获取到我们需要的数据的流,然后通过reduce的计算,将我们需要的数据给返回。
5.13 mapToInt
如果需要将Stream
类型,可以使用mapToInt。
Integer占用的内存比int多,再Stream流操作中,会自动装箱和拆箱。
// IntStream内部操作的是int类型,可以节省内存,减少自动装箱
IntStream intStream = Stream.of(1, 2, 5, 4, 3).mapToInt((Integer n) -> {
return n.intValue();
});
5.14 concat
如果有两个流,希望合并成一个流,可以使用Stream接口的静态方法concat
- 两个流合并之后,不能操作之前的流了。
- 只支持两个流的合并,不支持直接多次合并
Stream a = Stream.of("a");
Stream b = Stream.of("b");
Stream concat = Stream.concat(a, b);
concat.forEach(System.out::println);
6. 收集Stream流中的结果
对流操作完成后,如需要将流的结果保存到数组或者集合中,可以收集流中的数据。
6.1 收集到集合
Stream stream = Stream.of("aa", "bb", "cc");
// 将数据收集到集合中
List collect1 = stream.collect(Collectors.toList());
System.out.println(collect1); // [aa, bb, cc]
Set collect2 = stream.collect(Collectors.toSet());
System.out.println(collect1); // [aa, bb, cc]
// 收集到指定的ArrayList集合中
ArrayList collect = stream.collect(Collectors.toCollection(ArrayList::new));
// 收集到指定的HashSet集合中
HashSet collect = stream.collect(Collectors.toCollection(HashSet::new));
6.1 收集到数组
// 转成Object数组,不方便
Stream stream = Stream.of("aa", "bb", "cc");
Object[] objects = stream.toArray();
for (Object object : objects) {
System.out.println(object);
}
// 转成String数组
String[] strings = stream.toArray(String[]::new);
6.3 对流中数据进行聚合计算
处理是,可以像数据库中的聚合函数一样对某个字段进行操作,如最大值maxBy,最小值minBy,求和summingInt,平均值averagingInt,统计数量counting。
仍然是stream的collect方法参数里面的Collectors的方法。
6.4 对流中的数据进行分组Collectors.groupingBy
6.5 对流中的数据进行多级分组
先根据一个字段进行分组,然后再根据另一个字段进行分组。
6.6 对流中的数据进行分区
Collectors.partitioningBy会根据值是否为true,把集合分割成两个列表,一个true列表,一个false列表。
6.7 对流中的数据进行拼接
Collectors.joining会根据指定的连接符,将所有元素连接成一个字符串。
一个参数就是拼接处是这个字符。
三个参数则是有前缀和后缀的拼接。
- 到集合中:Collectors.toList()/toSet()/toCollection()
- 到数组中:toArray()/toArray(int[]::new)
- 聚合计算:Collectors.maxBy/minBy/counting/summingInt/averagingInt
- 分组:Collectors.groupingBy
- 分区:Collectors.partitioningBy
- 拼接:Collectors.joining
7. 并行的Stream流
7.1 串行的Stream流
上面的使用的Stream流都是串行的,就是在一个线程中执行。
7. 2 并行的Stream流的两种方式
// 1、通过parallelStream直接获取
ArrayList list = new ArrayList();
Stream stream1 = list.parallelStream();
// 2、将串行流转换成并行流
Stream parallel = Stream.of(1, 3, 4, 7).parallel();
7.3 parallelStream的线程安全问题
7.3.1 解决方法1:同步代码块
ArrayList list = new ArrayList<>();
Object obj = new Object();
IntStream.rangeClosed(0,1000)
.parallel()
.forEach(i->{
synchronized (obj){
list.add(i);
}
});
System.out.println(list.size());
7.3.2 解决方法2:线程安全的集合
// 这两个都是线程安全的集合
Vector v = new Vector();
List integers = Collections.synchronizedList(list);
IntStream.rangeClosed(0,1000)
.parallel()
.forEach(i->{
synchronized (obj){
v.add(i);
}
});
System.out.println(v.size());
7.3.3 解决方法3:调用Stream流的collect/toArray
List collect = IntStream.rangeClosed(0, 1000)
.parallel()
.boxed()
.collect(Collectors.toList());
System.out.println(collect.size());