stream 流式计算

简介

新时代程序员必须掌握:lambda 表达式、链式编程、函数式接口、Stream 流式计算

为什么要使用流式计算?

大数据: 存储+计算
集合、MySQL本质就是存储东西的;
计算都应该交给流来操作!

现在通过下面的题来了解链式编程。

/**
 * 题目要求:现在有5个用户!筛选;
 * 1、ID 必须是偶数
 * 2、年龄必须大于23岁
 * 3、用户名转为大写字母
 * 4、用户名字母倒着排序
 * 5、只输出一个用户!
 */

@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
    private int id;
    private String name;
    private int age;
}

过滤

filter(完成id是偶数,年龄大于23的过滤)

  • Stream filter(Predicate<? super T> predicate)
  • 一般适用于list集合,主要作用就是模拟sql查询,从集合中查询想要的数据。(过滤)
  • 我们可以看到参数是一个函数式接口
public class StreamExample {
    public static void main(String[] args) {
        User user1 = new User(1, "a", 21);
        User user2 = new User(2, "b", 26);
        User user3 = new User(3, "c", 23);
        User user4 = new User(4, "d", 24);
        User user5 = new User(5, "e", 25);
        List<User> list = Arrays.asList(user1, user2, user3, user4, user5);
        //流式计算
        list.stream()
                .filter(user -> {return user.getId()%2 == 0;})
                .filter(user -> {return user.getAge()>23;})
                 .forEach(System.out::println);
    }
}
结果如下
User(id=2, name=b, age=26)
User(id=4, name=d, age=24)

map

map 输出的名字大写

  • Stream map(Function<? super T,? extends R> mapper)
  • Stream中map可用于元素类型转化方法
  • 我们可以看到参数是一个函数式接口
public class StreamExample {
    public static void main(String[] args) {
        User user1 = new User(1, "a", 21);
        User user2 = new User(2, "b", 26);
        User user3 = new User(3, "c", 23);
        User user4 = new User(4, "d", 24);
        User user5 = new User(5, "e", 25);
        List<User> list = Arrays.asList(user1, user2, user3, user4, user5);
        //流式计算
        list.stream()
                .filter(user -> {return user.getId()%2 == 0;})
                .filter(user -> {return user.getAge()>23;})
                .map(user -> {return user.getName().toUpperCase();})
                 .forEach(System.out::println);
    }
}
结果
B
D

flatMap

  1. 使用flatMap方法的效果是,各个数组并不是分别映射成一个流,而是映射成流的内容。
  2. 所有使用map(Arrays::stream)时生成的单个流都被合并起来,即扁平化为一个流。
  3. 一言以蔽之,flatmap方法让你把一个流中的每个值都换成另一个流,然后把所有的流连接起来成为一个流。
@Test
    public void testFlatMap(){
        /**
         * 将一个单词数组中用到的字母不重复的列出来
         * 思路:
         * 我们不能直接使用.map(word -> word.split("")).distinct()
         * 因为这样操作的是一个数组中每个元素
         * 因此我们需要将数组全部元素转换为字符流,再去重
         * 我们也可以使用flatMap来完成上述要求
         */
        List<String> words = Arrays.asList("Hello","World");
        words.stream()
                .map(word -> word.split(""))
                .flatMap(Arrays::stream)
                .distinct()
                .forEach(System.out::print);
    }

结果:HeloWrd

mapToInt、mapToDouble和mapToLong

当我们使用reduce等方式进行求和的时候,其实暗地里进行了拆箱操作(Integer->int),这样非常损耗性能。
Stream流提供了IntStream、DoubleStream和LongStream,分别将流中的元素特化为int、long和double,从而避免了暗含的装箱成本。
将流转换为特化版本的常用方法是mapToInt、mapToDouble和mapToLong。

//累加求和:必须使用mapToInt后才能使用sum对整数进行累加求和。直接使用sum,stream流是没有这个方法的。
@Test
    public void testMapToInter(){
        List<Integer> list=new ArrayList<>();
        list.add(8);
        list.add(4);
        list.add(3);
        list.add(5);
        list.add(2);
        int a=list.stream()
                .mapToInt(e->e)
                .sum();//请注意,如果流是空的,sum默认返回0
        System.out.println(a);
    }
    //使用boxed可以将被mapToInt转换为的int类型的数值流转换为非特化的流Stream。
     @Test
    public void testBoxed(){
        List<Integer> list=new ArrayList<>();
        list.add(8);
        list.add(4);
        list.add(3);
        list.add(5);
        list.add(2);
        Stream<Integer> a=list.stream()
                .mapToInt(e->e)
                .boxed();//在需要将数值范围装箱成为一个一般流时,boxed尤其有用。

        a.forEach(System.out::print);

    }

排序

sorted 用sorted完成倒序(默认是顺序)

  • Stream sorted()
  • 返回由此流的元素组成的流,根据自然顺序排序。
  • 如果该流的元件不是Comparable ,一个java.lang.ClassCastException执行终端操作时,可以抛出。
  • 对于有序流(list),排序稳定。 对于无序的流,不能保证稳定性。
public class StreamExample {
    public static void main(String[] args) {
        User user1 = new User(1, "a", 21);
        User user2 = new User(2, "b", 26);
        User user3 = new User(3, "c", 23);
        User user4 = new User(4, "d", 24);
        User user5 = new User(5, "e", 25);
        List<User> list = Arrays.asList(user1, user2, user3, user4, user5);
        //流式计算
        list.stream()
                .filter(user -> {return user.getId()%2 == 0;})
                .filter(user -> {return user.getAge()>23;})
                .map(user -> {return user.getName().toUpperCase();})
                .sorted((u1,u2)->{return u2.compareTo(u1);})
                 .forEach(System.out::println);
    }
}
结果
D
B

limit

limit 只输出一个用户

  • Stream limit(long maxSize)
  • 返回由该流的元素组成的流,截断长度不能超过maxSize
public class StreamExample {
    public static void main(String[] args) {
        User user1 = new User(1, "a", 21);
        User user2 = new User(2, "b", 26);
        User user3 = new User(3, "c", 23);
        User user4 = new User(4, "d", 24);
        User user5 = new User(5, "e", 25);
        List<User> list = Arrays.asList(user1, user2, user3, user4, user5);
        //流式计算
        list.stream()
                .filter(user -> {return user.getId()%2 == 0;})
                .filter(user -> {return user.getAge()>23;})
                .map(user -> {return user.getName().toUpperCase();})
                .sorted((u1,u2)->{return u2.compareTo(u1);})
                .limit(1)
                 .forEach(System.out::println);
    }
}
结果
D

skip(n):跳过前n个元素

public static void main(String[] args) {
        User user1 = new User(1, "a", 21);
        User user2 = new User(2, "b", 26);
        User user3 = new User(3, "c", 23);
        User user4 = new User(4, "d", 24);
        User user5 = new User(5, "e", 25);
        List<User> list = Arrays.asList(user1, user2, user3, user4, user5);
        list.stream()
                .skip(2)
                .forEach(System.out::println);
    }

结果只打印了 user3,user4,user5(跳过了user1和uer2两个元素)

去重

distinct()去重

public static void main(String[] args) {
        User user1 = new User(1, "a", 21);
        User user2 = new User(2, "b", 26);
        User user3 = new User(3, "c", 23);
        User user4 = new User(4, "d", 24);
        User user5 = new User(5, "e", 25);
        List<User> list = Arrays.asList(user1, user2, user3, user4, user5,user5);
        list.stream()
                .distinct()
                .forEach(System.out::println);
    }

结果:虽然添加了两个user5,但只打印了一个user5

匹配相关元素

anyMatch :是否包含一个名字为c的用户

   @Test
    public void test(){
        User user1 = new User(1, "a", 21);
        User user2 = new User(2, "b", 26);
        User user3 = new User(3, "c", 23);
        User user4 = new User(4, "d", 24);
        User user5 = new User(5, "e", 25);
        List<User> list = Arrays.asList(user1, user2, user3, user4, user5);
        boolean b=list.stream()
                .anyMatch(e->e.getName().equals("c"));
        System.out.println(b);//true
    }

allMatch()年龄是否全部大于23

@Test
    public void testAllMatch(){
        User user1 = new User(1, "a", 21);
        User user2 = new User(2, "b", 26);
        User user3 = new User(3, "c", 23);
        User user4 = new User(4, "d", 24);
        User user5 = new User(5, "e", 25);
        List<User> list = Arrays.asList(user1, user2, user3, user4, user5);
        boolean b=list.stream()
                .allMatch(e->e.getAge()>23);
        System.out.println(b);//false
    }

noneMatch() 没有年龄<15的元素

@Test
    public void noneMatch(){
        User user1 = new User(1, "a", 21);
        User user2 = new User(2, "b", 26);
        User user3 = new User(3, "c", 23);
        User user4 = new User(4, "d", 24);
        User user5 = new User(5, "e", 25);
        List<User> list = Arrays.asList(user1, user2, user3, user4, user5);
        boolean b=list.stream()
                .noneMatch(e->e.getAge()<15);//true
        System.out.println(b);
    }

查找

findAny 找到满足年龄大于23的任意一个用户

@Test
    public void testFindAn(){
        User user1 = new User(1, "a", 21);
        User user2 = new User(2, "b", 26);
        User user3 = new User(3, "c", 23);
        User user4 = new User(4, "d", 24);
        User user5 = new User(5, "e", 25);
        List<User> list = Arrays.asList(user1, user2, user3, user4, user5);
        //Optional<T>类(java.util.Optional)是一个容器类,代表一个值存在或不存在
        Optional<User> optional =list.stream()
                .filter(e->e.getAge()>23)
                .findAny();

        //option包含值就返回true,否则返回false
        System.out.println(optional.isPresent());//true
        //optional.ifPresent会在值存在的时候执行给定的代码
        optional.ifPresent(e->{
            System.out.println(optional);//Optional[User(id=2, name=b, age=26)]
        });
        //get 会在值存在的时候返回值,否则抛出异常
        System.out.println(optional.get());
        //会在值存在的时候返回值,否则返回一个默认值
        System.out.println(optional.orElse(new User(6,"f",22)));

    }
true
Optional[User(id=2, name=b, age=26)]
User(id=2, name=b, age=26)
User(id=2, name=b, age=26)

findFirst 给定一个数字列表,下面的代码能找出第一个平方能被3整除的数:

@Test
    public void TestFindFirst(){
        List<Integer> someNumbers = Arrays.asList(1, 2, 3, 4, 5);
        Optional<Integer> firstSquareDivisibleByThree =
                someNumbers.stream()
                        .map(x -> x * x)
                        .filter(x -> x % 3 == 0)
                        .findFirst();// 9
        firstSquareDivisibleByThree.ifPresent(e->{
            System.out.println(firstSquareDivisibleByThree);
        });
    }

reduce 归约

reduce 累加,累乘,最小值,元素个数

public static void main(String[] args) {
        //累加求和和累成求和
        int sum=0;
        int sum2=1;
        for (int i = 1; i <= 10; i++) {
            sum+=i;
            sum2*=i;
        }
        System.out.println(sum);//55
        System.out.println(sum2);//3662800
        /**
         * 总和变量的初始值:sum==0
         * 将元素结合到一起的操作是+或*
         */
    }

    @Test
    public void testReduce01(){
        /**
         * 使用reduce的方式进行累加求和
         * 总和变量的初始值:sum==0
         * 将元素结合到一起的操作是+
         */
        List<Integer> list= Arrays.asList(1,2,3,4,5,6,7,8,9,10);
        int sum=list.stream()
                .reduce(0,(a,b)->a+b);
        System.out.println(sum);//55

        //同理:1到10累乘法
        int sum2=list.stream()
                .reduce(1,(a,b)->a*b);
        System.out.println(sum2);//3628800
    }

    @Test
    public void test02(){
        /**
         * Integer类中,正好有一个静态累加求和的方法,
         * 我们可以直接使用,就不用写表达式了。
         */
        List<Integer> list= Arrays.asList(1,2,3,4,5,6,7,8,9,10);
        int sum=list.stream()
                .reduce(0,Integer::sum);
        System.out.println(sum);//55
    }

    @Test
    public  void test03(){
        /**
        *不设置总和初始变量的方式进行累加求和
         * 我们可以使用个Optional对象对象。
         * 因为个Optional对象可以表明如果元素进行累加的情况,所有不需要初始值
         * 没有初始值就会包空指针异常
         */
        List<Integer> list= Arrays.asList(1,2,3,4,5,6,7,8,9,10);

        Optional<Integer> optional=list.stream()
                .reduce(Integer::sum);
        System.out.println(optional);//55

    }

    //求最小值
    @Test
    public void test04(){
        List<Integer> list= Arrays.asList(1,2,3,4,5,6,7,8,9,10);
        int min=list.stream()
                //此种方式必须要用初始值,我们可以设置集合任意一个元素作为初始值。
                //不能是集合以外的值,不然初始值比集合的最小值小,就会返回初始值而不是集合最小值
                .reduce(list.get(2),(a,b)-> a>b?b:a);
                //或:  .reduce(list.get(2),Integer::min);
        System.out.println(min);
    }
    //还可以这样求最小值
    @Test
    public void TestMin(){
        List<Integer> list=new ArrayList<>();
        list.add(8);
        list.add(4);
        list.add(3);
        list.add(5);
        list.add(2);
        Optional<Integer> a=list.stream()
                .min(Integer::compareTo);
        System.out.println(a);
    }

//求流中元素个数
    @Test
    public void test05(){
        List<Integer> list= Arrays.asList(1,2,3,4,5,6,7,8,9,10);
        int size=list.stream()
                .map(d->1) //将每项值都设为1,累加就是流中元素个数
                .reduce(0,(a,b)->a+b);
        System.out.println(size);
        System.out.println(list.size());
    }

    //字符串拼接:虽然也可以使用累加的方式reduce("", (n1, n2) -> n1 + n2),但是那样做效率不高(会不断的创建string)。
    //可以使用.collect(joining()),joining底层是StringBulider。
    @Test
    public void testJoin(){
        String[] str={"a","b","c","d"};
        List<String> list= Arrays.asList(str);
        String list1=list.stream()
                .collect(joining());
        System.out.println(list1);
    }

创建流的方式

值创建流Stream.of

//由值创建流
    @Test
    public void testStreamOf(){
        //我们可以通过stream.of()直接由值创建流
        Stream<String> stringStream=Stream.of("HOW","ARE","YOU");
        stringStream
                .map(e->e+" ")
                .map(String::toLowerCase)
                .forEach(System.out::print);
        //可以使用empty得到一个空流
        Stream stream=Stream.empty();
    }

数组创建流Arrays.stream(arr)

 @Test
    public void testArrayStream(){
        int a[]={4,9,3,5,2,6};
        int sum= Arrays.stream(a).sum();//累加求和
        System.out.println(sum);
    }

文件创建流

//由文件创建流
    @Test
    public void testFileStream(){
        long differentWord=0;
        try {
            //Files.lines,它会返回一个由指定文件中的各行构成的字符串流。
            Stream<String> lines= Files.lines(Paths.get("D:/a.txt"), Charset.defaultCharset());
            differentWord=lines
                    .flatMap(line->Arrays.stream(line.split("")))
                    .distinct()
                    .count();
        } catch (IOException e) {
            e.printStackTrace();
        }
        System.out.println(differentWord);
    }

无限流

 //函数生成流(无限流)1:迭代
    @Test
    public void testIterate(){
        Stream.iterate(0,n->n+2)
                .limit(10)//使用limit来进行限制,避免无限打印
                .forEach(System.out::println);
    }
    //函数生成流(无限流)2:生成
    @Test
    public void testGenerate(){
        //与iterate方法类似,generate方法也可让你按需生成一个无限流
        //但generate不是依次对每个新生成的值应用函数的
        //它接受一个Supplier<T>类型的Lambda提供新的值
        Stream.generate(Math::random)
                .limit(10)
                .forEach(System.out::println);
    }

分组

将人按照男女性别进行分组

    @Test
    public void testGroupingBy(){
        People people1 = new People( "a", 21,"女");
        People people2 = new People( "b", 26,"男");
        People people3 = new People( "c", 23,"女");
        People people4 = new People( "d", 24,"男");
        People people5 = new People( "e", 25,"男");
        List<People> list = Arrays.asList(people1, people2, people3, people4, people5);

        Map<String,List<People>> group=list.stream()
                .collect(Collectors.groupingBy(People::getSex));
        System.out.println(group);
       
    }
 {女=[People(name=a, age=21, sex=女), People(name=c, age=23, sex=女)], 男=[People(name=b, age=26, sex=男), People(name=d, age=24, sex=男), People(name=e, age=25, sex=男)]}

按照年龄进行多级分组

    @Test
    public void testGroupingBy2(){
        People people1 = new People( "a", 21,"女");
        People people2 = new People( "b", 14,"男");
        People people3 = new People( "c", 56,"女");
        People people4 = new People( "d", 40,"男");
        People people5 = new People( "e", 70,"男");
        List<People> list = Arrays.asList(people1, people2, people3, people4, people5);
        Map<String,List<People>> group=list.stream()
                .collect(Collectors.groupingBy(
                        person->{
                            if(person.getAge()<20){
                                return "青年";
                            }else if(person.getAge()>=20&&person.getAge()<=40){
                                return "壮年";
                            }else {
                                return "中老年";
                            }
                        }
                ));
        System.out.println(group);
     }
{青年=[People(name=b, age=14, sex=男)], 中老年=[People(name=c, age=56, sex=女), People(name=e, age=70, sex=男)], 壮年=[People(name=a, age=21, sex=女), People(name=d, age=40, sex=男)]}

计算分组后每组的数量

@Test
    public void testGroupingBy3(){
        People people1 = new People( "a", 21,"女");
        People people2 = new People( "b", 26,"男");
        People people3 = new People( "c", 23,"女");
        People people4 = new People( "d", 24,"男");
        People people5 = new People( "e", 25,"男");
        List<People> list = Arrays.asList(people1, people2, people3, people4, people5);

        Map<String,Long> group=list.stream()
                .collect(Collectors.groupingBy(People::getSex,Collectors.counting()));
        System.out.println(group);
        //{女=2, 男=3}
    }

取每一组的最高值

    @Test
    public void testGroupingBy4(){
        People people1 = new People( "a", 21,"女");
        People people2 = new People( "b", 26,"男");
        People people3 = new People( "c", 23,"女");
        People people4 = new People( "d", 24,"男");
        People people5 = new People( "e", 25,"男");
        List<People> list = Arrays.asList(people1, people2, people3, people4, people5);
        //这个Map中的值是Optional,因为这是maxBy工厂方法生成的收集器的类型
        Map<String, Optional<People>> group=list.stream()
                .collect(Collectors.groupingBy(People::getSex,Collectors.maxBy(Comparator.comparingInt(People::getAge))));
        System.out.println(group);
        //{女=Optional[People(name=c, age=23, sex=女)], 男=Optional[People(name=b, age=26, sex=男)]}
    }

取每一组的最高值(推荐)

    /**
     * 取每一组的最高值(推荐)
     * 上面的方式存在的问题:
     * groupingBy收集器只有在应用分组条件后,第一次在流中找到某个键对应的元素时才会把键加入分组Map中。
     * 这意味着Optional包装器在这里不是很有用,
     * 因为它不会仅仅因为它是归约收集器的返回类型而表达一个最终可能不存在却意外存在的值。
     */
    @Test
    public void testGroupingBy05(){
        People people1 = new People( "a", 21,"女");
        People people2 = new People( "b", 26,"男");
        People people3 = new People( "c", 23,"女");
        People people4 = new People( "d", 24,"男");
        People people5 = new People( "e", 25,"男");
        List<People> list = Arrays.asList(people1, people2, people3, people4, people5);
        //这个Map中的值是Optional,因为这是maxBy工厂方法生成的收集器的类型
        Map<String, People> group=list.stream()
                .collect(Collectors.groupingBy(People::getSex,Collectors.collectingAndThen(Collectors.maxBy(Comparator.comparingInt(People::getAge)),Optional::get)));
        System.out.println(group);
        //{女=Optional[People(name=c, age=23, sex=女)], 男=Optional[People(name=b, age=26, sex=男)]}
    }

对每一组的年龄求和

 @Test
    public void testGroupingBy06(){
        People people1 = new People( "a", 21,"女");
        People people2 = new People( "b", 26,"男");
        People people3 = new People( "c", 23,"女");
        People people4 = new People( "d", 24,"男");
        People people5 = new People( "e", 25,"男");
        List<People> list = Arrays.asList(people1, people2, people3, people4, people5);

        Map<String,Integer> group=list.stream()
                .collect(Collectors.groupingBy(People::getSex,Collectors.summingInt(People::getAge)));
        System.out.println(group);
        //{女=44, 男=75}
    }

联合mapping收集器进行使用

    @Test
    public void testGroupingBy07(){
        People people1 = new People( "a", 21,"女");
        People people2 = new People( "b", 14,"男");
        People people3 = new People( "c", 56,"女");
        People people4 = new People( "d", 40,"男");
        People people5 = new People( "e", 70,"男");
        List<People> list = Arrays.asList(people1, people2, people3, people4, people5);
        Map<String,List<Object>> group=list.stream()
                .collect(Collectors.groupingBy(
                        People::getSex,Collectors.mapping(
                                person->{
                                    if(person.getAge()<20){
                                        return person.getName()+"是青年";
                                    }else if(person.getAge()>=20&&person.getAge()<=40){
                                        return person.getName()+"是壮年";
                                    }else {
                                        return person.getName()+"是中老年";
                                    }
                                },
                                Collectors.toList()
                        )
                ));
        System.out.println(group);
        //{女=[a是壮年, c是中老年], 男=[b是青年, d是壮年, e是中老年]}
        //mapping这个方法接受两个参数:一个函数对流中的元素做变换,另一个则将变换的结果对象收集起来。
    }

分区

  • 分区是分组的特殊情况:由一个谓词(返回一个布尔值的函数)作为分类函数,它称分区函数。
  • 分区函数返回一个布尔值,这意味着得到的分组Map的键类型是Boolean,于是它最多可以分为两组——true是一组,false是一组。

将男和女分别对应boolean的ture和false,产生两个区域

    @Test
    public void testPartitioningBy() {
        People people1 = new People("a", 21, "女");
        People people2 = new People("b", 14, "男");
        People people3 = new People("c", 56, "女");
        People people4 = new People("d", 40, "男");
        People people5 = new People("e", 70, "男");
        List<People> list = Arrays.asList(people1, people2, people3, people4, people5);

        Map<Boolean, List<People>> list1 = list.stream()
                .collect(Collectors.partitioningBy(p -> {
                    if (p.getSex().equals("女")) {
                        return false;
                    } else {
                        return true;
                    }
                }));
        System.out.println(list1);
    }
  {false=[People(name=a, age=21, sex=女), People(name=c, age=56, sex=女)], true=[People(name=b, age=14, sex=男), People(name=d, age=40, sex=男), People(name=e, age=70, sex=男)]}

通过分区产生一个二级map

@Test
    public void testPartitioningBy02() {
        People people1 = new People("a", 21, "女");
        People people2 = new People("b", 14, "男");
        People people3 = new People("c", 56, "女");
        People people4 = new People("d", 40, "男");
        People people5 = new People("e", 70, "男");
        List<People> list = Arrays.asList(people1, people2, people3, people4, people5);

        Map<Boolean,Map<String,List<People>>> list1=list.stream()
                .collect(Collectors.partitioningBy(p->{
                    if (p.getSex().equals("女")) {
                        return false;
                    } else {
                        return true;
                    }
                },Collectors.groupingBy(People::getSex)));

        System.out.println(list1);
    }
 {false={女=[People(name=a, age=21, sex=女), People(name=c, age=56, sex=女)]}, true={男=[People(name=b, age=14, sex=男), People(name=d, age=40, sex=男), People(name=e, age=70, sex=男)]}}

找到男生和女生中年龄最大的人

    @Test
    public void testPartitioningBy03(){
        People people1 = new People("a", 21, "女");
        People people2 = new People("b", 14, "男");
        People people3 = new People("c", 56, "女");
        People people4 = new People("d", 40, "男");
        People people5 = new People("e", 70, "男");
        List<People> list = Arrays.asList(people1, people2, people3, people4, people5);
        Map<Boolean,People> list1=list.stream()
                .collect(
                        Collectors.partitioningBy(p-> {
                            if (p.getSex().equals("女")) {
                                return false;
                            } else {
                                return true;
                            }
                        },Collectors.collectingAndThen(
                                Collectors.maxBy(Comparator.comparingInt(People::getAge)),
                                Optional::get
                        ))
                );
        System.out.println(list1);
      //{false=People(name=c, age=56, sex=女), true=People(name=e, age=70, sex=男)}
    }

并行流

  • 简单理解并行流就是用来提升执行效率的。
  • 并行流并不总是比顺序流快。
  • 留意装箱。自动装箱和拆箱操作会大大降低性能。Java 8中有原始类型流(IntStream、LongStream、DoubleStream)来避免这种操作,但凡有可能都应该用这些流。
  • 有些操作本身在并行流上的性能就比顺序流差。特别是limit和findFirst等依赖于元素顺序的操作,它们在并行流上执行的代价非常大。
  • 对于较小的数据量,选择并行流几乎从来都不是一个好的决定。
  • 流是否适用于并行的总结如下:
 ArrayList        极佳               
 LinkedList        差                   
 IntStream.range  极佳                    
 Stream.iterate    差                
 HashSet           好                 
 TreeSet           好                 

使用传统的方式对n个数字求和

    @Test
    public void testSum01(){
        long sum=0;
        //此处我们对100个数字进行求和
        for (int i = 0; i <= 100; i++) {
            sum+=i;
        }
        System.out.println(sum);//5050
    }

使用普通的流式计算进行求和

    @Test
    public void testSum02(){
        int sum=Stream.iterate(0,i->i+1)
                .limit(101)
                .reduce(0,Integer::sum);
        System.out.println(sum);//5050
    }

使用并行流的方式进行求和

    @Test
    public void testSum03(){
        /**
         * 当数据非常大的时候通过使用并行流可以大大提升效率
         * 因为并行流的思想就是将任务拆分为很多小任务,计算完成在汇总。
         * fork-join
         * 并行流默认使用的是ForkJoinPool线程池
         * 默认的线程数就是电脑的处理器数量
         * 可以通过下面的方式来改变来改变电脑处理器的默认数量作为线程数(建议不改)
         * System.setProperty("java.util.concurrent.ForkJoinPool.common.parallelism","12");
         */
        int sum=Stream.iterate(0,i->i+1)
                /**
                 * 不建议limit和parallel一起使用
                 * 有些操作本身在并行流上的性能就比顺序流差。
                 * 特别是limit和findFirst等依赖于元素顺序的操作,
                 * 它们在并行流上执行的代价非常大
                 */
                .limit(101)
                .parallel()
                .reduce(0,Integer::sum);
        System.out.println(sum);//5050
    }

错误使用并行流

    @Test
    public void testErrorParallel() {
        class Accumulator {
            public int sum = 0;

            public void add(long val) {
                sum += val;
            }
        }
        Accumulator accumulator = new Accumulator();
        LongStream.rangeClosed(1, 100)
                .parallel()
                .forEach(accumulator::add);
        System.out.println(accumulator.sum);
        //多次运行获得的结果有5050,4700
        //而我们只需要5050这一个结果,为什么会产生上面的情况呢
        /**
         * 这时因为调用的sum方法sum+=val;不是一个原子操作
         * sum是一个线程共享的变量,线程相互修改,就会造成错误。
         * 如何更改:改为原子类将 将sum的int改为原子型AtomicInteger,这样每个线程修改后,其它线程就能读取到正确的值
         * 除此之外我们在使用并行流的时候需要尽量避免使用共享变量
         */
    }

正确的程序如下,但不建议这样做,因为并行流的目的就是加快速度,使用原子类会得不偿失

   @Test
   public void TestTrue(){
        class Accumulator {
            public AtomicLong sum=new AtomicLong();

            public void add(long val) {
                sum.getAndAdd(val);
            }
        }
        Accumulator accumulator = new Accumulator();
        LongStream.rangeClosed(1, 100)
                .parallel()
                .forEach(accumulator::add);
        System.out.println(accumulator.sum);//5050
    }
posted @ 2021-04-28 09:14  懒鑫人  阅读(502)  评论(0编辑  收藏  举报