work hard work smart

专注于Java后端开发。 不断总结,举一反三。
  博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

java 8 Stream流编程

Posted on 2021-01-18 13:35  work hard work smart  阅读(239)  评论(0编辑  收藏  举报

一、基本概念

1、Stream是一个高级的迭代器,不是数据结构,不是一个集合,不会存放数据。关注的是数据高效的处理,数据在一个流水线中执行。

2、外部迭代和内部迭代

public class StreamDemo1 {
    public static void main(String[] args) {
        int[] nums = {1, 2, 4};
        // 外部迭代
        int sum = 0;
        for(int i : nums){
            sum += i;
        }
        System.out.println("结果为:" + sum);

        //使用stream的内部迭代
        int sum2 = IntStream.of(nums).sum();
        System.out.println("结果为:" + sum2);

    }
}

  

3、中间操作/终止操作和惰性求值

        //使用stream的内部迭代
        //map就是中间操作,每个数字乘以2 (返回stream的操作)
        //sum 就是终止操作
        int sum2 = IntStream.of(nums).map(i -> i * 2).sum();
        System.out.println("结果为:" + sum2);   //结果为:14

  

4、惰性求值

 

 

 返回类型是流,就是中间操作; 返回类型是结果,就是终止操作。

 

二、流创建

 

 如下面的代码

public class StreamDemo2 {
    public static void main(String[] args) {
       List<String> list = new ArrayList<>();
       //1、从集合创建
       list.stream();
       list.parallelStream(); //并行流

        // 2、从数组创建
        Arrays.stream(new int[]{2,5,8});

        // 3、创建数字流
        IntStream.of(1,5,8);
        IntStream.rangeClosed(1,10); // 1到10的数字流

        //使用random创建一个无限流, 并指定limit
        new Random().ints().limit(10);

        // 4. 自己产生流
        Random random = new Random();
        Stream.generate(() -> random.nextInt()).limit(20);


    }


}

  

三、流的中间操作

包括无状态操作和有状态操作。

无状态表示操作跟前后没有依赖关系。

有状态表示结果要依赖于其它元素。如排序操作,依赖所有的元素,才能计算结果。

1、把每个单词的长度打印出来

 public static void main(String[] args) {
       String str = "my name is larry";

       //把每个单词的长度打印出来
       Stream.of(str.split(" ")).filter(s -> s.length() > 2).map(s -> s.length()).forEach(System.out::println);

    }  

filter 过滤长度大于2的字符

map: 每个单词的长度

 

2、flatmap使用

public static void main(String[] args) {
       String str = "my name is larry";

       //把每个单词的长度打印出来
       Stream.of(str.split(" ")).filter(s -> s.length() > 2).map(s -> s.length()).forEach(System.out::println);

       //flatMap  A -> B属性(是个集合),最终得到A元素里面所有B属性集合。
        // intStream/longStream 并不是Stream的子类,所以要进行装箱boxed
        Stream.of(str.split(" ")).flatMap( s -> s.chars().boxed()).forEach(
                i -> System.out.println((char)i.intValue()));;
    }

  

输出结果

m
y
n
a
m
e
i
s
l
a
r
r
y

  

这里A就是单词,B就是单词的字符。

 

3、peek使用

 public static void main(String[] args) {
       String str = "my name is larry";

        //peek 用于debug,是个中间操作,和forEach是终止操作
        System.out.println("-------------peek-----------------");
        Stream.of(str.split(" ")).peek(System.out::println).forEach(System.out::println);
    }

  

输出结果为:

-------------peek-----------------
my
my
name
name
is
is
larry
larry

  

4、limit操作

        // limit使用,主要用于无限流
        new Random().ints().filter(i -> i > 100 && i < 1000).limit(10)
                .forEach(System.out::println);  

输出结果:

273
628
601
769
528
114
676
871
531
862

  

四、Stream流编程--终止操作

分为非断路操作和短路操作

 常用的终止操作

public class StreamDemo4 {
    public static void main(String[] args) {
       String str = "my name is larry";

        // 使用并行流
        str.chars().parallel().forEach(i -> System.out.print((char)i)); // 输出结果是乱的:  mnrmyay laries
        System.out.println();
        // 使用forEachOrdered保证顺序
        str.chars().parallel().forEachOrdered(i -> System.out.print((char)i)); // // 输出结果是正确的:  my name is larry

        //收集到list
        System.out.println();
        List<String> list = Stream.of(str.split(" ")).collect(Collectors.toList());
        System.out.println(list); //输出结果: [my, name, is, larry]

        // 使用reduce拼接字符串
        Optional<String> letters = Stream.of(str.split(" ")).reduce((s1, s2) -> s1 + "|" + s2);
        System.out.println(letters.orElse("")); // 输出结果: my|name|is|larry


        // 带有初始化值的reduce
       String reduce = Stream.of(str.split(" ")).reduce("",(s1, s2) -> s1 + "|" + s2);
       System.out.println(reduce);

       // 计算所有单词总长度
        Integer length = Stream.of(str.split(" ")).map(s -> s.length()).reduce(0, (s1, s2) -> s1 + s2);
        System.out.println(length); // 输出结果: 13

        // max的适应
        Optional<String> max = Stream.of(str.split(" ")).max((s1, s2) -> s1.length() - s2.length());
        System.out.println(max.get()); //输出结果: larry

        // 使用findFirst 短路操作
        OptionalInt findFirst = new Random().ints().findFirst();
        System.out.println(findFirst.getAsInt()); // 输出结果: 1554210202

    }
    
}

  

 五、并行流

1、串行流

public class StreamDemo5 {
    public static void main(String[] args) {
        //串行打印
        IntStream.range(1,100).peek(StreamDemo5::debug).count();

    }

    public static  void debug(int i){
        try {
            Date date = new Date();  // 获取当前系统时间
            SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");  // 设置日期格式
            String strTime = simpleDateFormat.format(date);  // 格式转换
            System.out.println(strTime + ":debug " + i);
            TimeUnit.SECONDS.sleep(3);
        }catch (InterruptedException e){
            e.printStackTrace();
        }

    }

} 

输出结果:可以发现,打印是串行的,没隔3秒钟打印一条

2021-01-18 10:03:43:debug 1
2021-01-18 10:03:46:debug 2
2021-01-18 10:03:49:debug 3
2021-01-18 10:03:52:debug 4

 

2、并行流  

将上面代码增加parallel()

 //调用parallel 产生一个并行流
 IntStream.range(1,100).parallel().peek(StreamDemo5::debug).count(); 

 输出结果:

2021-01-18 10:10:29:debug 81
2021-01-18 10:10:29:debug 4
2021-01-18 10:10:29:debug 7
2021-01-18 10:10:29:debug 16
2021-01-18 10:10:29:debug 90
2021-01-18 10:10:29:debug 43
2021-01-18 10:10:29:debug 31
2021-01-18 10:10:29:debug 65
2021-01-18 10:10:32:debug 91
2021-01-18 10:10:32:debug 32
2021-01-18 10:10:32:debug 66
2021-01-18 10:10:32:debug 82
2021-01-18 10:10:32:debug 5
2021-01-18 10:10:32:debug 8
2021-01-18 10:10:32:debug 17
2021-01-18 10:10:32:debug 44  

可以发现,每次打印了8条。这个是CPU个数一致的。

 

3、先并行流,再串行流

public class StreamDemo5 {
    public static void main(String[] args) {
       
        // 先并行,再串行
        IntStream.range(1,100)
                //调用parallel 产生一个并行流
                .parallel().peek(StreamDemo5::debug)
                //调用sequential 产生一个串行流
                .sequential().peek(StreamDemo5::debug2)
                .count();
    }

    public static  void debug(int i){
        try {
            Date date = new Date();  // 获取当前系统时间
            SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");  // 设置日期格式
            String strTime = simpleDateFormat.format(date);  // 格式转换
            System.err.println(strTime + ":debug " + i);
            TimeUnit.SECONDS.sleep(3);
        }catch (InterruptedException e){
            e.printStackTrace();
        }

    }

    public static  void debug2(int i){
        try {
            Date date = new Date();  // 获取当前系统时间
            SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");  // 设置日期格式
            String strTime = simpleDateFormat.format(date);  // 格式转换
            System.out.println(strTime + ":debug2 " + i);
            TimeUnit.SECONDS.sleep(3);
        }catch (InterruptedException e){
            e.printStackTrace();
        }

    }

}

 输出结果:

2021-01-18 10:16:43:debug 1
2021-01-18 10:16:46:debug2 1
2021-01-18 10:16:49:debug 2
2021-01-18 10:16:52:debug2 2
2021-01-18 10:16:55:debug 3
2021-01-18 10:16:58:debug2 3

 结论:  多次调用 parallel / sequential, 以最后一次调用为准

 

4、并行流使用的线程池

public class StreamDemo5 {
    public static void main(String[] args) {

        //并行流使用的线程池: ForkJoinPool.commonPool(JDK自带的线程池)
        // 默认的线程数是 当前机器的cpu个数
        System.setProperty("java.util.concurrent.ForkJoinPool.common.parallelism","4");
        IntStream.range(1,100).parallel().peek(StreamDemo5::debug).count();
    }

    public static  void debug(int i){
        try {
            Date date = new Date();  // 获取当前系统时间
            SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");  // 设置日期格式
            String strTime = simpleDateFormat.format(date);  // 格式转换
            System.err.println("[" + Thread.currentThread().getName() + "] " +strTime + ":debug " + i);
            TimeUnit.SECONDS.sleep(3);
        }catch (InterruptedException e){
            e.printStackTrace();
        }

    }

}  

输出结果:

[ForkJoinPool.commonPool-worker-2] 2021-01-18 10:30:38:debug 87
[ForkJoinPool.commonPool-worker-0] 2021-01-18 10:30:38:debug 81
[main] 2021-01-18 10:30:38:debug 62
[ForkJoinPool.commonPool-worker-1] 2021-01-18 10:30:38:debug 31
[ForkJoinPool.commonPool-worker-3] 2021-01-18 10:30:38:debug 13
[ForkJoinPool.commonPool-worker-3] 2021-01-18 10:30:41:debug 14
[ForkJoinPool.commonPool-worker-1] 2021-01-18 10:30:41:debug 32
[ForkJoinPool.commonPool-worker-2] 2021-01-18 10:30:41:debug 88
[main] 2021-01-18 10:30:41:debug 63
[ForkJoinPool.commonPool-worker-0] 2021-01-18 10:30:41:debug 82  

每次输出4条

 

5、创建自己的线程池

public class StreamDemo5 {
    public static void main(String[] args) {

        //使用自己的线程池,不使用默认线程池,防止任务被阻塞
        //线程名称为: ForkJoinPool-1
        ForkJoinPool pool = new ForkJoinPool(5);
        pool.submit(() ->  IntStream.range(1,100).parallel()
                .peek(StreamDemo5::debug).count());
        pool.shutdown();

        synchronized (pool){
            try {
                pool.wait();
            }catch (InterruptedException e){
                e.printStackTrace();
            }
        }
    }

    public static  void debug(int i){
        try {
            Date date = new Date();  // 获取当前系统时间
            SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");  // 设置日期格式
            String strTime = simpleDateFormat.format(date);  // 格式转换
            System.err.println("[" + Thread.currentThread().getName() + "] " +strTime + ":debug " + i);
            TimeUnit.SECONDS.sleep(3);
        }catch (InterruptedException e){
            e.printStackTrace();
        }

    }

}  

输出结果:

[ForkJoinPool-1-worker-3] 2021-01-18 10:38:13:debug 31
[ForkJoinPool-1-worker-5] 2021-01-18 10:38:13:debug 56
[ForkJoinPool-1-worker-1] 2021-01-18 10:38:13:debug 65
[ForkJoinPool-1-worker-2] 2021-01-18 10:38:13:debug 90
[ForkJoinPool-1-worker-4] 2021-01-18 10:38:13:debug 81
[ForkJoinPool-1-worker-4] 2021-01-18 10:38:16:debug 82
[ForkJoinPool-1-worker-3] 2021-01-18 10:38:16:debug 32
[ForkJoinPool-1-worker-2] 2021-01-18 10:38:16:debug 91
[ForkJoinPool-1-worker-1] 2021-01-18 10:38:16:debug 66
[ForkJoinPool-1-worker-5] 2021-01-18 10:38:16:debug 57  

使用线程名称为: ForkJoinPool-1,不是默认的线程池了。

 

六、收集器

收集器就是把流处理后的数据收集起来,可以收集成集合类,如List,Set。或者把处理后的数据进行再处理。

enum Gender{
    MALE, FEMALE
}

enum Grade{
    ONE,
    TWO,
    THREE,
    FOUR
}

class Student {
    private String name;
    private int age;
    private Gender gender;
    private Grade grade;

    public Student(String name, int age, Gender gender, Grade grade) {
        this.name = name;
        this.age = age;
        this.gender = gender;
        this.grade = grade;
    }

    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 Gender getGender() {
        return gender;
    }

    public void setGender(Gender gender) {
        this.gender = gender;
    }

    public Grade getGrade() {
        return grade;
    }

    public void setGrade(Grade grade) {
        this.grade = grade;
    }

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", gender=" + gender +
                ", grade=" + grade +
                '}';
    }
}

public class CollectDemo {

    public static void main(String[] args) {
        List<Student> students = Arrays.asList(
                new Student("张三", 10, Gender.FEMALE, Grade.FOUR),
                new Student("李四", 8, Gender.MALE, Grade.ONE),
                new Student("王五", 18, Gender.MALE, Grade.THREE),
                new Student("赵六", 20, Gender.MALE, Grade.TWO)
        );

        //1、得到所有学生的年龄列表
        // s -> s.getAge()  -->  Student::getAge, 不会多生成一个类似lamda$0 这样的函数
        List<Integer> ages = students.stream().map(Student::getAge).collect(Collectors.toList());
        System.out.println("所有学生的年龄:" + ages); // 输出结果: 所有学生的年龄:[10, 8, 18, 20]


        // //输出结果: 年龄汇总信息:IntSummaryStatistics{count=4, sum=56, min=8, average=14.000000, max=20}
        //2、 count=4 总共4个学生, 所有学生的年龄和为56, 最小值为min=8, 最大值为max=20, 平均值为 average=14.000000
        IntSummaryStatistics agesSummaryStatistics = students.stream().collect(Collectors.summarizingInt(Student::getAge));
        System.out.println("年龄汇总信息:" + agesSummaryStatistics);

        //3、 分块
        Map<Boolean, List<Student>> genders = students.stream().collect(
                Collectors.partitioningBy(s -> s.getGender() == Gender.MALE));
        //System.out.println("男女学生列表:" + genders);
        //System.out.println();
        // 引入依赖 commons-collections4-4.1.jar
        MapUtils.verbosePrint(System.out, "男女学生列表", genders);

        // 4. 分组 按班级分组
        Map<Grade, List<Student>> grades = students.stream().collect(Collectors.groupingBy(Student::getGrade));
        MapUtils.verbosePrint(System.out, "学生班级列表", grades);

        // 5、得到所有班级学生的个数
        Map<Grade,Long> gradesCount = students.stream().collect(
                Collectors.groupingBy(Student::getGrade, Collectors.counting()));
        MapUtils.verbosePrint(System.out, "每个班级学生个数列表", gradesCount);

    }
   
    
    
}

  

输出结果如下:

所有学生的年龄:[10, 8, 18, 20]
年龄汇总信息:IntSummaryStatistics{count=4, sum=56, min=8, average=14.000000, max=20}
男女学生列表 = 
{
    false = [Student{name='张三', age=10, gender=FEMALE, grade=FOUR}]
    true = [Student{name='李四', age=8, gender=MALE, grade=ONE}, Student{name='王五', age=18, gender=MALE, grade=THREE}, Student{name='赵六', age=20, gender=MALE, grade=TWO}]
}
学生班级列表 = 
{
    ONE = [Student{name='李四', age=8, gender=MALE, grade=ONE}]
    FOUR = [Student{name='张三', age=10, gender=FEMALE, grade=FOUR}]
    THREE = [Student{name='王五', age=18, gender=MALE, grade=THREE}]
    TWO = [Student{name='赵六', age=20, gender=MALE, grade=TWO}]
}
每个班级学生个数列表 = 
{
    ONE = 1
    FOUR = 1
    THREE = 1
    TWO = 1
}

  

 七、流的运行机制

1、测试代码如下:

 public static void main(String[] args) {

        Random random = new Random();
        //随机产生数据
        Stream<Integer> integerStream = Stream.generate(() -> random.nextInt())
                // 产生10个(无限流需要短路操作)
                .limit(10)
                // 第1个无状态操作
                .peek(s -> System.out.println("无状态操作1 peek:" + s))
                // 第2个无状态操作
                .filter(s -> {
                    System.out.println("无状态操作2 filter: " + s);
                    return s > 1;
                })
                });
        //终止操作
        integerStream.count();
    }

输出结果:

 

无状态操作1 peek:958678073
无状态操作2 filter: 958678073
无状态操作1 peek:-1254433080
无状态操作2 filter: -1254433080
无状态操作1 peek:-1544631259
无状态操作2 filter: -1544631259
无状态操作1 peek:-949435277
无状态操作2 filter: -949435277
无状态操作1 peek:-1380758868
无状态操作2 filter: -1380758868
无状态操作1 peek:-437107324
无状态操作2 filter: -437107324
无状态操作1 peek:-617414841
无状态操作2 filter: -617414841
无状态操作1 peek:-685899581
无状态操作2 filter: -685899581
无状态操作1 peek:-568187900
无状态操作2 filter: -568187900
无状态操作1 peek:514107384
无状态操作2 filter: 514107384

 

  

总结:所有操作是链式调用,一个元素只迭代一次

 

2、Debug代码

 

 

 

每一个中间操作返回一个新的流。流里面有一个属性sourceStage 指向同一个地方,就是Head

 

 

 Head-> nextStage -> nextStage ->  ... -> null

这就是实现链式调用的过程

 

3、两个无状态操作后,增加一个有状态,再增加一个无状态操作

 public static void main(String[] args) {

        Random random = new Random();
        //随机产生数据
        Stream<Integer> integerStream = Stream.generate(() -> random.nextInt())
                // 产生10个(无限流需要短路操作)
                .limit(10)
                // 第1个无状态操作
                .peek(s -> System.out.println("无状态操作1 peek:" + s))
                // 第2个无状态操作
                .filter(s -> {
                    System.out.println("无状态操作2 filter: " + s);
                    return s > 1;
                })
                //有状态操作
                .sorted((i1,i2) -> {
                    System.out.println("排序:" + i1 +  "," +i2);
                    return i1.compareTo(i2);
                })
                // 第3个无状态操作
                .peek(s -> {
                    System.out.println("无状态操作3 peek2:" + s);
                });
        //终止操作
        integerStream.count();
    }

 

输出结果如下:

无状态操作1 peek:1212039829
无状态操作2 filter: 1212039829
无状态操作1 peek:-824860534
无状态操作2 filter: -824860534
无状态操作1 peek:1647394626
无状态操作2 filter: 1647394626
无状态操作1 peek:-989892865
无状态操作2 filter: -989892865
无状态操作1 peek:1679831720
无状态操作2 filter: 1679831720
无状态操作1 peek:429970807
无状态操作2 filter: 429970807
无状态操作1 peek:-820772493
无状态操作2 filter: -820772493
无状态操作1 peek:-1270158375
无状态操作2 filter: -1270158375
无状态操作1 peek:51717906
无状态操作2 filter: 51717906
无状态操作1 peek:-412303194
无状态操作2 filter: -412303194
排序:1647394626,1212039829
排序:1679831720,1647394626
排序:429970807,1679831720
排序:429970807,1647394626
排序:429970807,1212039829
排序:51717906,1647394626
排序:51717906,1212039829
排序:51717906,429970807
无状态操作3 peek2:51717906
无状态操作3 peek2:429970807
无状态操作3 peek2:1212039829
无状态操作3 peek2:1647394626
无状态操作3 peek2:1679831720

 

总结: 有状态操作会把无状态操作截断,单独处理

 

其它: 1、并行环境下,有状态的中间操作不一定能并行操作

       2、 parallel/sequetial 这2个操作也是中间操作(也是返回stream)

    但是他们不创建流,他们只修改Head的并行标志。