Java8新特性 Stream流详解

news/2024/5/19 12:04:56 标签: java, 开发语言, Java8新特性, Stream

目录

1、介绍

2、获取Stream流的两种方式

方式一:根据Collection获取流

方式二:Stream中的静态方法of获取流

区别

3、Stream流注意事项

4、Stream流的常用方法

forEach

count

filter

limit

skip

map

sorted

distinct

match

find

max和min

reduce

map与reduce组合使用

mapToInt

concat

5、收集Stream流中的结果

将流中的数据收集到集合中

将流中的数据收集到数组中

对流中数据进行聚合计算

对流中数据进行分组

对流中数据进行分区

对流中数据进行拼接

6、并行的Stream

获取并行Stream流的两种方式

第一种:直接获取并行的Stream

第二种:将串行流转成并行流

串行与并行Stream代码对比

parallelStream线程安全问题

解决方案一:同步代码块

解决方案二:使用线程安全的集合

解决方案三:使用Collections.synchronizedList方法将集合转为线程安全的

解决方案四:调用Stream流的collect/toArray

parallelStream底层

Fork/join框架介绍

Fork/join原理-分治法

Fork/join原理-工作窃取算法


1、介绍

Stream流思想类似于工厂车间的流水线,Stream流不是一种数据结构,不保存数据,而是对数据进行加工处理。

先看一段代码:我们会发现这样又循环又创建新的集合再存进去,很麻烦

java">public class Y {
    public static void main(String[] args) {
        List<String> nameList = new ArrayList<>();
        Collections.addAll(nameList,"张无忌","周芷若","赵敏","说不得","张乐");

        //拿到所有姓张的放进zhangList中
        List<String> zhangList = new ArrayList<>();
        List<String> threeList = new ArrayList<>();
        for(String name : nameList){
            if(name.startsWith("张")){
                zhangList.add(name);
            }
        }

        //遍历姓张的,拿出名字长度等于3的
        for(String name : zhangList){
            if(name.length() == 3){
                threeList.add(name);
            }
        }
        System.out.println(threeList);
    }
}

Stream流优化写法:

java">List<String> nameList = new ArrayList<>();
Collections.addAll(nameList,"张无忌","周芷若","赵敏","说不得","张乐");

nameList.stream()
    .filter((s) -> {
        return s.startsWith("张");
    })
    .filter((s) -> {
        return s.length() == 3;
    })
    .forEach((s) -> {
        System.out.println(s);
    });

2、获取Stream流的两种方式

方式一:根据Collection获取流

java">//方式一:根据Collection获取流
List<String> list = new ArrayList<>();
Stream<String> stream = list.stream();

方式二:Stream中的静态方法of获取流

java">Stream<String> aa = Stream.of("张无忌","周芷若","赵敏","说不得","张乐");

区别

前者(of)是把集合当做一个整体处理,后者是把一个个元素分开来遍历。所以要对集合中每个元素做判断过滤,要用后者list.stream。

3、Stream流注意事项

(1)Stream只能操作一次

(2)Stream方法返回的是新的流

(3)Stream不调用终结方法,中间的操作不会执行

(4)如果Stream流的一些方法(limit、filter、skip等...)返回值是Stream,那么就得在最后调用终结方法(forEach、count)。

4、Stream流的常用方法

forEach

java">public class TestLambda2 {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        Collections.addAll(list,"苏格拉底","柏拉图","亚里士多德","叔本华","尼采");
        //完整版
        list.stream().forEach((String str) -> {
            System.out.println(str);
        });

        //精简版
        list.stream().forEach(str -> System.out.println(str));
    }
}

count

统计其中的元素个数。

java">List<String> list = new ArrayList<>();
Collections.addAll(list,"苏格拉底","柏拉图","亚里士多德","叔本华","尼采");
//完整版
long count = list.stream().count();
System.out.println(count);

filter

用于过滤数据,返回符合条件的数据。

java">public class TestLambda2 {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        Collections.addAll(list,"苏格拉底","柏拉图","亚里士多德","叔本华","尼采");
        //得到名字长度为3个字的人
        //完整版
        list.stream().filter(str -> {
           return str.length() == 3;
        }).forEach(System.out::println);

        //精简版
        list.stream().filter(str -> str.length() == 3).forEach(System.out::println);
    }
}

limit

可以进行截取,只取用前n个。

java">public class TestLambda2 {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        Collections.addAll(list,"苏格拉底","柏拉图","亚里士多德","叔本华","尼采");

        //获取前3个元素
        list.stream().limit(3).forEach(System.out::println);
    }
}

skip

如果希望跳过前几个元素,可以使用skip方法获取一个截取之后的新流。

java">public class TestLambda2 {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        Collections.addAll(list,"苏格拉底","柏拉图","亚里士多德","叔本华","尼采");

        //获取前3个元素
        list.stream().skip(3).forEach(System.out::println);
    }
}

map

其实就是一种类型的流转换为另一种类型的流。

java">public class TestLambda3 {
    public static void main(String[] args) {
        Stream<String> stream = Stream.of("1","2","3");
        //将Stream流中的字符串转为Integer类型(完整版)
        stream.map((String str) -> {
            return Integer.parseInt(str);
        }).forEach(System.out::println);

        //简化版
        stream.map(str -> Integer.parseInt(str)).forEach(System.out::println);
    }
}

sorted

如果需要将数据排序,可以使用sorted方法。有如下两种方法API:

java">Stream<T> sorted();  //根据元素的自然顺序排序
Stream<T> sorted(Comparator<? super T> comparator); //根据比较器指定的规则排序
java">public class TestLambda3 {
    public static void main(String[] args) {
        Stream<Integer> stream = Stream.of(6,2,5,1,8,7);
        //升序排列
        stream.sorted().forEach(System.out::println);
        //降序排列
        stream.sorted((i1,i2) -> i2 - i1).forEach(System.out::println);
    }
}

distinct

去除重复数据。

java">public class TestLambda3 {
    public static void main(String[] args) {
        Stream<Integer> stream = Stream.of(6,1,1,8,8,7,10);
        //基本类型去重
        stream.distinct().forEach(System.out::println);
        //自定义类型去重(必须重写equals和hashcode方法)
        Stream<Person> streamPerson = Stream.of(
                new Person("李隆基",40),
                new Person("杨玉环",20),
                new Person("杨玉环",20),
                new Person("李白",45),
                new Person("高力士",35));
        streamPerson.distinct().forEach(System.out::println);
    }
}

match

断言

java">public class TestLambda3 {
    public static void main(String[] args) {
        Stream<Integer> stream = Stream.of(6,1,8,7,10);
        //匹配所有元素,判断所有元素中是否都大于3
        boolean b = stream.allMatch(i -> i > 3);
        //匹配某个元素,只要有其中一个元素满足即可
        boolean b1 = stream.anyMatch(i -> i > 3);
        //匹配所有元素,所有元素都不满足条件
        boolean b2 = stream.noneMatch(i -> i > 3);
        System.out.println(b); //false
        System.out.println(b1); //true
        System.out.println(b2); //false
    }
}

find

找到某些数据。

java">public class TestLambda3 {
    public static void main(String[] args) {
        Stream<Integer> stream = Stream.of(6,1,8,7,10);
        //找到第一个元素:6
        Optional<Integer> first = stream.findFirst();
        System.out.println(first.get()); //6
    }
}

max和min

java">public class TestLambda3 {
    public static void main(String[] args) {
        Stream<Integer> stream = Stream.of(6,1,8,7,10);
        Optional<Integer> max = stream.max((o1, o2) -> o1 - o2);
        System.out.println(max.get()); //10

        Optional<Integer> min = stream.min((o1, o2) -> o1 - o2);
        System.out.println(min.get()); //1
    }
}

reduce

如果需要将所有数据归纳得到一个数据,可以使用reduce方法。

java">public class TestLambda3 {
    public static void main(String[] args) {
        Stream<Integer> stream = Stream.of(6,1,8,7,10);
        Integer reduce = stream.reduce(0, (x, y) -> {
            System.out.println("x = " + x + ",y = " + y);
            return x + y;
        });
        System.out.println(reduce); //32
    }
}

map与reduce组合使用

案例1:求出所有年龄的总和

java">public class TestLambda3 {
    public static void main(String[] args) {
        Integer reduce = Stream.of(
                new Person("李隆基", 40),
                new Person("杨玉环", 20),
                new Person("李白", 45),
                new Person("高力士", 35))
                .map((Person p) -> {
                    return p.getAge();
                }).reduce(0, (x, y) -> {
                    return x + y;
                });
        System.out.println("所有人总年龄为:" +  reduce); //140
    }
}

案例2:找出最大年龄

java">public class TestLambda3 {
    public static void main(String[] args) {
        Integer reduce = Stream.of(
                new Person("李隆基", 40),
                new Person("杨玉环", 20),
                new Person("李白", 45),
                new Person("高力士", 35))
                .map(p -> p.getAge())
                .reduce(0,(x,y) -> x > y?x : y);
        System.out.println("最大年龄是:" + reduce); //45
    }
}

mapToInt

如果需要将Stream<Integer>中的Integer类型数据转成int类型,可以使用此方法。

java">public class TestLambda3 {
    public static void main(String[] args) {
        Stream<Integer> stream = Stream.of(1,2,3,4,5);
        //返回的IntStream流,内部操作的是int类型数据,就可以节省内存,减少自动装箱和拆箱
        IntStream intStream = stream.mapToInt(Integer::intValue);
    }
}

concat

如果有两个流,希望合并成为一个流,那么可以使用Stream接口的静态方法concat。

java">public class TestLambda3 {
    public static void main(String[] args) {
        Stream<String> stream1 = Stream.of("黑格尔");
        Stream<String> stream2 = Stream.of("第欧根尼");
        Stream<String> newStream = Stream.concat(stream1, stream2);
        newStream.forEach(System.out::println);
    }
}

5、收集Stream流中的结果

将流中的数据收集到集合中

java">public class TestLambda3 {
    public static void main(String[] args) {
        Stream<String> stream = Stream.of("aa","bb","cc","dd","ee");
        //最终结果收集到集合中
        List<String> collect = stream.limit(3).collect(Collectors.toList());
        System.out.println(collect);  //[aa, bb, cc]
    }
}

将流中的数据收集到数组中

java">public class TestLambda3 {
    public static void main(String[] args) {
        Stream<String> stream = Stream.of("aa","bb","cc","dd","ee");
        //最终结果收集到数组中
        String[] strings = stream.toArray(String[]::new);
    }
}

对流中数据进行聚合计算

当我们使用Stream流处理数据后,可以像数据库的聚合函数一样对某个字段进行操作。比如获取最大值、获取最小值、求总和、平均值、统计数量。

java">public class TestLambda3 {
    public static void main(String[] args) {
        Stream<Person> streamPerson = Stream.of(
                new Person("李隆基",40),
                new Person("杨玉环",20),
                new Person("李白",45),
                new Person("高力士",35));
        //获取最大年龄
        Optional<Person> collectMax = streamPerson.collect(Collectors.maxBy((s1, s2) -> s1.getAge() - s2.getAge()));
        System.out.println(collectMax.get());
        //获取最小年龄
        Optional<Person> collectMin = streamPerson.collect(Collectors.minBy((s1, s2) -> s1.getAge() - s2.getAge()));
        System.out.println(collectMin.get());
        //获取总年龄
        Integer collectSum = streamPerson.collect(Collectors.summingInt(s -> s.getAge()));
        System.out.println(collectSum);
        //获取年龄平均值
        Double collectAvg = streamPerson.collect(Collectors.averagingInt(s -> s.getAge()));
        System.out.println(collectAvg);
    }
}

对流中数据进行分组

java">public class TestLambda3 {
    public static void main(String[] args) {
        Stream<Person> streamPerson = Stream.of(
                new Person("李隆基",40),
                new Person("杨玉环",20),
                new Person("安禄山",20),
                new Person("李白",45),
                new Person("白居易",45),
                new Person("高力士",35));
        //根据年龄进行分组
        Map<Integer, List<Person>> map = streamPerson.collect(Collectors.groupingBy((p) -> p.getAge()));
        map.forEach((k,v) -> {
            System.out.println(k + ":" + v);
        });
    }
}

java">public class TestLambda3 {
    public static void main(String[] args) {
        Stream<Person> streamPerson = Stream.of(
                new Person("李隆基",40),
                new Person("杨玉环",20),
                new Person("安禄山",20),
                new Person("李白",45),
                new Person("白居易",45),
                new Person("高力士",35));
        Map<String, List<Person>> map = streamPerson.collect(Collectors.groupingBy((p) -> {
            if(p.getAge() > 20){
                return "成年";
            }else{
                return "未成年";
            }
        }));
        map.forEach((k,v) -> {
            System.out.println(k + ":" + v);
        });
    }
}

对流中数据进行分区

Collectors.partitioningBy会根据值是否为true,把集合分隔为两个列表,一个true列表,一个false列表。

java">public class TestLambda3 {
    public static void main(String[] args) {
        Stream<Person> streamPerson = Stream.of(
                new Person("李隆基",40),
                new Person("杨玉环",20),
                new Person("安禄山",20),
                new Person("李白",40));
        //年龄大于30的分为true区
        Map<Boolean, List<Person>> map = streamPerson.collect(Collectors.partitioningBy(p -> p.getAge() > 30));
        map.forEach((k,v) -> {
            System.out.println(k + ":" + v);
        });
    }
}

对流中数据进行拼接

Collectors.joining会根据指定的连接符,将所有元素连接成一个字符串。

java">public class TestLambda3 {
    public static void main(String[] args) {
        Stream<Person> streamPerson = Stream.of(
                new Person("李隆基",40),
                new Person("杨玉环",20),
                new Person("安禄山",20),
                new Person("李白",40));
        //根据一个字符串进行拼接
        String collect = streamPerson.map(Person::getName).collect(Collectors.joining("__"));
        System.out.println(collect); //李隆基__杨玉环__安禄山__李白

        //根据三个字符串进行拼接
        String collect = streamPerson.map(Person::getName).collect(Collectors.joining("__","@","。"));
        System.out.println(collect); //@李隆基__杨玉环__安禄山__李白。
    }
}

6、并行的Stream

注意:目前我们使用的Stream是串行的,就是在一个线程上执行的 。

获取并行Stream流的两种方式

第一种:直接获取并行的Stream
java">//第一种:直接获取并行的Stream流
List<String> list = new ArrayList<>();
Stream<String> stream01 = list.parallelStream();

第二种:将串行流转成并行流
java">//第二种:将串行流转成并行流
Stream<String> stream02 = list.stream().parallel();

串行与并行Stream代码对比

java">public class TestLambda3 {
    public static void main(String[] args) {
        //串行
        Stream<Integer> stream = Stream.of(5, 8, 1, 2, 7, 9);
        stream.filter(i -> {
            System.out.println(Thread.currentThread() + "  " + i);
            return i > 2;
        }).count();
    }
}

java">public class TestLambda3 {
    public static void main(String[] args) {
        //并行
        Stream<Integer> stream = Stream.of(5, 8, 1, 2, 7, 9);
        stream.parallel().filter(i -> {
            System.out.println(Thread.currentThread() + "  " + i);
            return i > 2;
        }).count();
    }
}

parallelStream线程安全问题

先看一个线程不安全的实例:

java">public class TestLambda3 {
    public static void main(String[] args) {
        List<Integer> list = new ArrayList<>();
        IntStream.rangeClosed(1,1000).parallel().forEach(i -> {
            list.add(i);
        });
        System.out.println(list.size()); //928
    }
}

首先总共1000条数据,最后打印的集合size也应该是1000啊,结果打印出928,说明在并行的情况下,使用ArrayList是线程是不安全的。

解决方案一:同步代码块
java">public class TestLambda3 {
    public static void main(String[] args) {
        Object obj = new Object();
        List<Integer> list = new ArrayList<>();
        IntStream.rangeClosed(1,1000).parallel().forEach(i -> {
            synchronized (obj){ //加入同步代码块
                list.add(i);
            }
        });
        System.out.println(list.size()); //1000
    }
}

解决方案二:使用线程安全的集合
java">public class TestLambda3 {
    public static void main(String[] args) {
        List<Integer> list = new Vector<>();
        IntStream.rangeClosed(1,1000).parallel().forEach(i -> {
            list.add(i);
        });
        System.out.println(list.size()); //1000
    }
}

解决方案三:使用Collections.synchronizedList方法将集合转为线程安全的
java">public class TestLambda3 {
    public static void main(String[] args) {
        List<Integer> list = new ArrayList<>();
        //将ArrayList转为线程安全的集合
        List<Integer> integers = Collections.synchronizedList(list);
        IntStream.rangeClosed(1,1000).parallel().forEach(i -> {
            integers.add(i);
        });
        System.out.println(list.size()); //1000
    }
}

解决方案四:调用Stream流的collect/toArray
java">public class TestLambda3 {
    public static void main(String[] args) {
        List<Integer> list = new ArrayList<>();
        List<Integer> collect = IntStream.rangeClosed(1, 1000).parallel().boxed().collect(Collectors.toList());
        System.out.println(collect.size()); //1000
    }
}

parallelStream底层

Fork/join框架介绍

parallelStream使用的是Fork/join框架。Fork/join框架自JDK1.7引入。Fork/join框架可以将一个大任务拆分为很多小任务来异步执行。

Fork/join框架主要包含三个模块:

1、线程池:ForkJoinPool

2、任务对象:ForkJoinTask

3、执行任务的线程:ForkJoinWorkerThread

Fork/join原理-分治法

ForkJoinPool主要用来使用分治法来解决问题。典型的应用比如快速排序算法,ForkJoinPool需要使用相对少的线程来处理大量的任务。比如要对1000万个数据进行排序,那么会将这个任务分割成两个500万的排序任务和一个针对这两组500万数据的合并任务。以此类推,对于500万的数据也会做出同样的分割处理,到最后会设置一个阈值来规定当数据规模到多少时,停止这样的分割处理。比如,当元素的数量小于10时,会停止分割,转而使用插入排序对他们进行排序。那么到最后,所有的任务加起来会有大概2000000+个。问题的关键在于,对于一个任务而言,只有当它所有的子任务完成之后,它才能够被执行。

Fork/join原理-工作窃取算法

Fork/join最核心的地方就是利用了现代硬件设备多核,在一个操作时候会有空闲的CPU,那么如何利用好这个空闲的CPU就成了提高性能的关键,在这里我们要提到工作窃取算法就是整个Fork/join框架的核心理念Fork/join工作窃取算法是指某个线程从其它队列窃取任务来执行

有的线程任务执行的比较快,就没事干了,这时就会窃取其它线程的任务来执行。


http://www.niftyadmin.cn/n/5302331.html

相关文章

pytorch06:权重初始化

目录 一、梯度消失和梯度爆炸1.1相关概念1.2 代码实现1.3 实验结果1.4 方差计算1.5 标准差计算1.6 控制网络层输出标准差为11.7 带有激活函数的权重初始化 二、Xavier方法与Kaiming方法2.1 Xavier初始化2.2 Kaiming初始化2.3 常见的初始化方法 三、nn.init.calculate_gain 一、…

针对CSP-J/S的冲刺训练:Day 2 基础题

一、审题 时间限制&#xff1a;1000ms 内存限制&#xff1a;256MB AC率&#xff1a;94.28% 题目描述 筛1&#xff5e;n之间的质数。 输入描述 一行&#xff0c;一个正整数n&#xff08;1 < n < 2147483647&#xff09;。 输出描述 一行&am…

3dmax灯光缓存参数应该怎么设置?

细分&#xff1a;用来决定灯光缓存的样本数量&#xff0c;样本数量以此数值的平方来计算。数值越高&#xff0c;效果越好&#xff0c;速度越慢。 一般出图建议1000到1800之间已经足够了 采样大小&#xff1a;用来控制灯光缓存的样本尺寸大小&#xff0c;较小的数值意味着较小的…

CSDN-2023年成长之路-Tech行者

真的很想拿到CSDN实体铭牌&#xff08;定制ID&#xff09;,求各位大佬多多指教&#xff0c;评论区见&#xff01;&#xff01;&#xff01; 目录 一、印象深刻的实战经历 1.1基于unity的智慧校园数据可视化 1.2遇到的问题 1.3一个案列的代码 二、系统学习新技术的心得体会…

Zabbix“专家坐诊”第222期问答汇总

问题一 Q&#xff1a;Zabbix6.4能讲下维护区间吗&#xff1f;启动区间和期间是什么不同&#xff1f; TiuySPzJ6567d4304f4b9.png A&#xff1a;启动区间是这条维护规则的有效时间&#xff1b;期间则是维护的周期&#xff0c;比如什么时候维护&#xff0c;维护多久。 问题二 …

c++学习:函数模板+实战

目录 函数模板 思考 如果两个参数的类型不一样可以下面这么写 如果有指定返回参数可以下面这么写 实战 找出三个数中最大的一个 函数模板 实际上就是建立一个通用函数&#xff0c;其函数返回值类型和形参类型不具体指定&#xff0c;用一个虚拟的类型来代表template 是一个…

CentOS:安装gitlab

1、安装依赖 yum install -y curl policycoreutils-python openssh-server #centos8没有policycoreutils-python yum源&#xff0c;不用管 2、启动ssh并设置为开机自启动 systemctl enable sshd systemctl start sshd 3、安装Postfix来发送通知邮件。 yum install postfix…

2024年MySQL学习指南(二),探索MySQL数据库,掌握未来数据管理趋势

文章目录 前言4. DDL- 操作数据库4.1 查询4.2 创建数据库4.3 删除数据库4.4 使用数据库 5. DDL- 操作数据表5.1 数据类型5.2 查询表5.3 创建表5.4 删除表5.5 修改表 6. 实战案例详解 前言 接上一篇文章【2024年MySQL学习指南&#xff08;一&#xff09;】 4. DDL- 操作数据库 …