Stream流

流基础知识

什么是流

是一组有序的数据序列,将数据从一个地方带到另一个地方。 。根据数据流向的不同,可以分为输入(Input)流和输出(Output)流两种。

流的作用

Java 中流的定义是“源中支持聚合操作的一系列元素”。流从诸如集合,数组或I / O资源之类的源中消耗。流支持功能编程语言的通用操作,例如映射,过滤,缩小,查找,排序等。

什么是输入流与输出流

Java 程序通过流来完成输入/输出,所有的输入/输出以流的形式处理。因此要了解 I/O 系统,首先要理解输入/输出流的概念。

输入就是将数据从各种输入设备(包括文件、键盘等)中读取到内存中,输出则正好相反,是将数据写入到各种输出设备(比如文件、显示器、磁盘等)。例如键盘就是一个标准的输入设备,而显示器就是一个标准的输出设备,但是文件既可以作为输入设备,又可以作为输出设备。

数据流是 Java 进行 I/O 操作的对象,它按照不同的标准可以分为不同的类别。

  • 按照流的方向主要分为输入流和输出流两大类。
  • 数据流按照数据单位的不同分为字节流和字符流。
  • 按照功能可以划分为节点流和处理流。

数据流的处理只能按照数据序列的顺序来进行,即前一个数据处理完之后才能处理后一个数据。数据流以输入流的形式被程序获取,再以输出流的形式将数据输出到其它设备。图 1 为输入流模式,图 2 为输出流模式。

在这里插入图片描述

在这里插入图片描述

Stream流的作用

Stream 中文称为 “流”,通过将集合转换为这么一种叫做 “流” 的元素序列,通过声明性方式,能够对集合中的每个元素进行一系列并行或串行的流水线操作。

函数式编程带来的好处尤为明显,这种代码更多的表达了业务逻辑的意图,而不是他的实现机制,易读的代码也易于维护,更可靠,更不容易出错。

流与集合的区别

    1. 流为特定元素类型的序列值提供接口。但是,与集合不同,流实际上并不存储元素。元素是按需计算的。可以将流视为延迟构造的Collection,可以在需要它们时计算其值。
    1. 流操作不会更改其源。相反,它们返回存储结果的新流。
    1. 可能无界。集合的大小是有限的,但是流没有。诸如limit(n)或findFirst()之类的短路操作可以允许对无限流的计算在有限时间内完成。
    1. 消耗品。在流的生存期内,流的元素仅被访问一次。如果要重新访问流中的同一元素,则需要根据源重新生成新的流。

中级运营与终端运营

中间操作将一个流转换为另一流,而终端操作则产生结果。在流上执行终端操作时,将无法再使用该流。
例如,在下面的代码中:

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());
        }
    }
测试

image.png

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);
        }
    }
测试结果

image.png

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());

测试结果
image.png

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);
        }
    }

测试

image.png

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 个元素

测试结果

image.png

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

测试

image.png

案例与相关方法演示

以下的内容均来自博客园博主牛牛的自留地的相关文章,我绝对写的不错,所以也添加在文章末尾
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

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值