流基础知识
什么是流
是一组有序的数据序列,将数据从一个地方带到另一个地方。 。根据数据流向的不同,可以分为输入(Input)流和输出(Output)流两种。
流的作用
Java 中流的定义是“源中支持聚合操作的一系列元素”。流从诸如集合,数组或I / O资源之类的源中消耗。流支持功能编程语言的通用操作,例如映射,过滤,缩小,查找,排序等。
什么是输入流与输出流
Java 程序通过流来完成输入/输出,所有的输入/输出以流的形式处理。因此要了解 I/O 系统,首先要理解输入/输出流的概念。
输入就是将数据从各种输入设备(包括文件、键盘等)中读取到内存中,输出则正好相反,是将数据写入到各种输出设备(比如文件、显示器、磁盘等)。例如键盘就是一个标准的输入设备,而显示器就是一个标准的输出设备,但是文件既可以作为输入设备,又可以作为输出设备。
数据流是 Java 进行 I/O 操作的对象,它按照不同的标准可以分为不同的类别。
- 按照流的方向主要分为输入流和输出流两大类。
- 数据流按照数据单位的不同分为字节流和字符流。
- 按照功能可以划分为节点流和处理流。
数据流的处理只能按照数据序列的顺序来进行,即前一个数据处理完之后才能处理后一个数据。数据流以输入流的形式被程序获取,再以输出流的形式将数据输出到其它设备。图 1 为输入流模式,图 2 为输出流模式。
Stream流的作用
Stream 中文称为 “流”,通过将集合转换为这么一种叫做 “流” 的元素序列,通过声明性方式,能够对集合中的每个元素进行一系列并行或串行的流水线操作。
函数式编程带来的好处尤为明显,这种代码更多的表达了业务逻辑的意图,而不是他的实现机制,易读的代码也易于维护,更可靠,更不容易出错。
流与集合的区别
-
- 流为特定元素类型的序列值提供接口。但是,与集合不同,流实际上并不存储元素。元素是按需计算的。可以将流视为延迟构造的Collection,可以在需要它们时计算其值。
-
- 流操作不会更改其源。相反,它们返回存储结果的新流。
-
- 可能无界。集合的大小是有限的,但是流没有。诸如limit(n)或findFirst()之类的短路操作可以允许对无限流的计算在有限时间内完成。
-
- 消耗品。在流的生存期内,流的元素仅被访问一次。如果要重新访问流中的同一元素,则需要根据源重新生成新的流。
中级运营与终端运营
中间操作将一个流转换为另一流,而终端操作则产生结果。在流上执行终端操作时,将无法再使用该流。
例如,在下面的代码中:
list.stream().filter(s -> s.length() > 2).count();
filter()是中间操作,而count()是终端操作。调用count()时,我们不能再使用该流
常用的中间操作包括:
筛选,映射,不同,排序,跳过,限制,flatMap
常用的终端操作包括:
**
1. forEach,toArray,收集,减少,计数,最小,最大
2. findFirst,findAny,anyMatch,allMatch,noneMatch
分类结构如何进行操作
三级分类就是面对一对多结构,查询主实体时需要附带主实体的子实体列表怎么写?
**
解决方案:查出主列表,循环查子列表
**
List的Stream流操作可以简化我们的代码,减少程序运行的压力,应对上面的问题,以前的话是先查出对应的list数据,然后根据取到集合中id去查找对应的子实体中数据,接着在放入对应的集合中去,key值表示主实体的id,value值表示对应主实体id查到的结合数据,这样就会三次foreach循环组装数据,会很麻烦,当数据量大的时候,会增加程序运行的负荷,造成运行缓慢。所以,流式操作代替我们的这一堆操作,提高了代码的简易性,可维护性,可靠性,更不容易出错。
案例与相关方法演示
测试环境搭建
创建一个person类
public class Person {
private String name;
private int age;
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 Person(String name, int age) {
this.name = name;
this.age = age;
}
}
首先我们先创建一个 Person 泛型的 List
List<Person> list = new ArrayList<>();
list.add(new Person("jack", 20));
list.add(new Person("mike", 25));
list.add(new Person("tom", 30));
1.stream() / parallelStream()
最常用到的方法,将集合转换为流
Stream<Person> stream = list.stream();
而 parallelStream() 是并行流方法,能够让数据集执行并行操作
2.filter(T->boolean)
保留boolean为true的元素,collect(toList()) 可以把流转换为 List 类型
保留年龄为 20 的 person 元素
list = list.stream()
.filter(person -> person.getAge() == 20)
.collect(toList());
打印输出 [Person{name='jack', age=20}]
@Test
public void ListStreamTest(){
List<Person> list = new ArrayList<>();
list.add(new Person("jack", 20));
list.add(new Person("mike", 25));
list.add(new Person("tom", 30));
/*将集合装换为流*/
Stream<Person> stream = list.stream();
/*filter(T->boolean)保留boolean为true的元素*/
/*.filter(person -> person.getAge() == 20)在流中查找age值为20的元素*/
/*.collect(Collectors.toList()将流装换为list类型*/
List<Person> FilterList = list.stream().filter(person -> person.getAge() == 20).collect(Collectors.toList());
for (Person person : FilterList) {
System.out.println(person.toString());
}
}
测试
3.distinct()
去除重复元素,这个方法是通过类的 equals 方法来判断两个元素是否相等的
如例子中的 Person 类,需要先定义好 equals 方法,不然类似[Person{name=‘jack’, age=20}, Person{name=‘jack’, age=20}] 这样的情况是不会处理的
在person类中复写equals方法
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Person person = (Person) o;
return age == person.age &&
Objects.equals(name, person.name);
}
@Test
public void ListStreamTest2(){
List<Person> list = new ArrayList<>();
list.add(new Person("jack", 20));
list.add(new Person("mike", 25));
list.add(new Person("tom", 30));
list.add(new Person("jack", 20));
List<Person> collect = list.stream().distinct().collect(Collectors.toList());
for (Person person : collect) {
System.out.println(person);
}
}
测试结果
4.sorted() / sorted((T, T) -> int)
如果流中的元素的类实现了 Comparable 接口,即有自己的排序规则,那么可以直接调用 sorted() 方法对元素进行排序,如 Stream
反之, 需要调用 sorted((T, T) -> int) 实现 Comparator 接口
@Test
public void ListStreamTest3(){
List<Person> list = new ArrayList<>();
list.add(new Person("jack", 20));
list.add(new Person("mike", 50));
list.add(new Person("tom", 30));
list.add(new Person("lining", 21));
List<Person> list1 = list.stream().sorted((p1, p2) -> p1.getAge() - p2.getAge()).collect(Collectors.toList());
for (Person person : list1) {
System.out.println(person);
}
}
当然这个可以简化为
list = list.stream()
.sorted(Comparator.comparingInt(Person::getAge))
.collect(toList());
测试结果
5.limit(long n)返回前 n 个元素
@Test
public void ListStreamTest4(){
List<Person> list = new ArrayList<>();
list.add(new Person("jack", 20));
list.add(new Person("mike", 50));
list.add(new Person("tom", 30));
list.add(new Person("lining", 21));
List<Person> list1 = list.stream().limit(2).collect(Collectors.toList());
for (Person person : list1) {
System.out.println(person);
}
}
测试
6.skip(long n)
去除前n个元素
@Test
public void ListStreamTest5(){
List<Person> list = new ArrayList<>();
list.add(new Person("jack", 20));
list.add(new Person("mike", 50));
list.add(new Person("tom", 30));
list.add(new Person("lining", 21));
List<Person> list1 = list.stream().limit(2).skip(1).collect(Collectors.toList());
for (Person person : list1) {
System.out.println(person);
}
}
tips:
用在 limit(n) 前面时,先去除前 m 个元素再返回剩余元素的前 n 个元素
limit(n) 用在 skip(m) 前面时,先返回前 n 个元素再在剩余的 n 个元素中去除 m 个元素
测试结果
7.map(T -> R)
将流中的每一个元素 T 映射为 R(类似类型转换)
List<String> newlist = list.stream().map(Person::getName).collect(toList());
newlist 里面的元素为 list 中每一个 Person 对象的 name 变量
8.flatMap(T -> Stream)
将流中的每一个元素 T 映射为一个流,再把每一个流连接成为一个流
List<String> list = new ArrayList<>();
list.add("aaa bbb ccc");
list.add("ddd eee fff");
list.add("ggg hhh iii");
list = list.stream().map(s -> s.split(" ")).flatMap(Arrays::stream).collect(toList());
上面例子中,我们的目的是把 List 中每个字符串元素以" "分割开,变成一个新的 List。
首先 map 方法分割每个字符串元素,但此时流的类型为 Stream<String[ ]>,因为 split 方法返回的是 String[ ] 类型;所以我们需要使用 flatMap 方法,先使用Arrays::stream将每个 String[ ] 元素变成一个 Stream 流,然后 flatMap 会将每一个流连接成为一个流,最终返回我们需要的 Stream
测试
案例与相关方法演示
以下的内容均来自博客园博主牛牛的自留地的相关文章,我绝对写的不错,所以也添加在文章末尾
Stream流的常用方法:https://www.cnblogs.com/liuyang-520/p/12902309.html
1、forEach
// forEach接收消费类型的函数式接口,为Stream流的最终方法,调用后不能再调用Stream流的其它方法了
// 若只有一个参数则可以省略()
// 参数的类型也可省略,java编译器会自动推断参数类型
// 若方法体只有一行代码则可以省略{}
@Test
public void test1() {
Stream<String> st = Stream.of("张三","李四","王五","赵六","田七");
st.forEach((String name) -> {
System.out.println(name);
});
}
@Test
public void test2() {
Stream<String> st = Stream.of("张三","李四","王五","赵六","田七");
st.forEach(name -> System.out.println(name));
}
2、filter
// 过滤,延迟方法,接收判断类型的函数式接口,产生新的子集流,调用后还可以继续调用其它的Stream流方法
// 若写了{}则必须用return返回结果
// 若省略了{}则不需要return返回结果
@Test
public void test1() {
// 原始Stream流
Stream<String> st1 = Stream.of("张三","张无忌","赵敏","张三丰","周芷若");
// 过滤后会产生一个新的子集流
Stream<String> st2 = st1.filter((String name) -> {
return name.startsWith("张") && name.length() == 3;
});
st2.forEach(name -> System.out.println(name));
}
@Test
public void test2() {
// 原始Stream流
Stream<String> st1 = Stream.of("张三","张无忌","赵敏","张三丰","周芷若");
// 过滤后会产生一个新的子集流
Stream<String> st2 = st1.filter(name -> name.startsWith("张") && name.length() == 3);
st2.forEach(name -> System.out.println(name));
}
3、map
// 延迟方法,操作Stream流数据中的每个元素,将Stream流映射到一个新的Stream流上
// 操作数据中的每个元素,改变该元素的值或者类型等等
@Test
public void test1() {
Stream<String> st1 = Stream.of("a","b","c","d","e");
// 操作数据中的每个元素,返回新的元素
// 方法体只有一行时,可省略{}和return
st1.map(str -> str += str.toUpperCase())
.forEach(str -> System.out.println(str));
}
@Test
public void test2() {
Stream<String> st1 = Stream.of("a","b","c","d","e");
// 操作数据中的每个元素,返回新的元素
// 方法体有{}时必须加上return
st1.map(str -> {
return str += str.toUpperCase();
}).forEach(str -> System.out.println(str));
}
@Test
public void test3() {
Stream<String> st1 = Stream.of("1","2","3","4","5");
// 操作数据中的每个元素,将每个元素转为Integer类型
Stream<Integer> st2 = st1.map(str -> Integer.parseInt(str));
st2.forEach(num -> System.out.println(num.getClass()));
}
4、count
// 最终方法,没有参数,没有方法体,属于Stream流的最终方法,用于统计Stream流中的数据长度,返回long类型
@Test
public void test4() {
// count方法属于Stream流的最终方法,统计数据的长度,返回long类型
Stream<String> st1 = Stream.of("1","2","3","4","5");
long ct = st1.map(str -> Integer.parseInt(str))
.filter(num -> num > 3)
.count();
System.out.println(ct);
}
5、limit
// 延迟方法,截取Stream流中的前几个元素返回新的Stream流,入参为long类型,没有方法体
// 若入参的值大于Stream流中的数据的长度则返回由原数据组成的新Stream流
@Test
public void test1() {
Stream<String> st1 = Stream.of("aa","bb","cc","dd","ee");
st1.limit(3).forEach(str -> System.out.println(str));
}
6、skip
// 延迟方法,入参为long类型,没有方法体,跳过前一个Stream流的前几个元素,得到由后面的元素组成的新Stream流
@Test
public void test2() {
Stream<String> st1 = Stream.of("aa","bb","cc","dd","ee");
st1.skip(2).forEach(str -> System.out.println(str));
}
7、concat
// Stream的静态方法,将多个Stream流的数据按入参顺序合并为一个新的Stream流
@Test
public void test3() {
Stream<String> st1 = Stream.of("aa","bb","cc","dd","ee");
Stream<String> st2 = Stream.of("AA","BB","CC","DD","EE");
Stream<String> st3 = Stream.concat(st1,st2);
st3.forEach(s -> System.out.println(s));
}
参考文章
List的Stream流操作:https://www.jianshu.com/p/24af4f3ab046
Stream流的常用方法:https://www.cnblogs.com/liuyang-520/p/12902309.html