Java8新增的Stream的性能如何

1. Stream简单介绍

1.1 概述

  • Stream是 Java 8新增加的类,用来补充集合类,它提供了过滤、映射、排序、聚合等方式来对集合进行数据操作。
  • Steam类在java.util包里,并且将steam方法加入了Collection接口里面。
    stream方法
    Stream类

1.2 特点

  • 函数式编程,使用Lambda表达式。
  • Stream可以构建一套流水线,并在没有调用结束方法的时候,不会进行数据操作,仅仅只是构建一个管道。
  • Stream可以是无限的,也可以是有限的。
  • Stream有串行也有并行。
  • Stream的数据操作不会对原集合进行修改。

2. 开胃小菜

2.1 简单使用

Person类

static class Person{
        private Integer id;
        private Integer age;

        public Person(Integer id) {
            this.id = id;
        }
        public Person(Integer id,Integer age) {
            this.id = id;
            this.age = age;
        }
        public Person() {}
        public Integer getId() {
            return id;
        }
        public void setId(Integer id) {
            this.id = id;
        }
        public Integer getAge() {
            return age;
        }
        public void setAge(Integer age) {
            this.age = age;
        }

        @Override
        public String toString() {
            return "Person{" +
                    "id=" + id +
                    ", age=" + age +
                    '}';
        }
    }

这里简单做三个操作:
1.使用steam里的循环对list的元素进行修改操作
2.使用steam的过滤,进行对age大于18的进行过滤操作
3.使用steam的映射,将persionList的id属性提取到新list里
代码如下:

public static void test() {
        List<Person> personList = new ArrayList<>();
        personList.add(new Person(1,10));
        personList.add(new Person(2,19));
        personList.add(new Person(3,50));
        System.out.println("初始化personList:");
        System.out.println(Arrays.toString(personList.toArray()));
        // 循环,为每个person的年龄加1
        personList.stream().forEach(person -> {
            person.setAge(person.getAge() + 1);
        });
        System.out.println("age加了之后的personList:");
        System.out.println(Arrays.toString(personList.toArray()));
        // 筛选出年龄大于18岁的person
        List<Person> collect = personList.stream().filter(p -> p.getAge() > 18).collect(Collectors.toList());
        System.out.println("筛选出年龄大于18岁的person:");
        System.out.println(Arrays.toString(collect.toArray()));
        // 取出person的id到一个新list
        List<Integer> idList = personList.stream().map(person -> person.getId()).collect(Collectors.toList());
        System.out.println("取出person的id到一个新list:");
        System.out.println(Arrays.toString(idList.toArray()));
        System.out.println("当前的personList:");
        System.out.println(Arrays.toString(personList.toArray()));
    }

代码执行结果如下:
在这里插入图片描述

  1. 可见使用Stream进行数据操作非常方便,以上的实现方式,大家可以尝试使用普通的for循环进行实现,看下需要多少行代码。
  2. 筛选的结果也是大于18岁的person。映射也成功活去了所有person的id。
  3. 并且验证了使用Stream不会对原集合的数据进行修改。

3. 简单验证性能

本文主题不是介绍如何使用Stream,上面只是简单介绍使用方法,并且引出接下来的性能对比测试。
本文主要对比两种操作,上面开胃菜里已经包含了:第一是循环操作,第二是映射操作,也就是map,并提供五组数据量的测试。测试代码如下:

public static void test2() {
        System.out.println("测试循环操作10000万个元素");
        List<Person> list = new ArrayList<>();
        for (int i=1; i<=10000; i++) {
            list.add(new Person(i));
        }
        long l1 = System.currentTimeMillis();
        for (int i = 0; i < list.size(); i++) {
            list.get(i).setAge(10);
        }
        long l2 = System.currentTimeMillis();
        list.stream().forEach(person -> {
            person.setAge(11);
        });
        long l3 = System.currentTimeMillis();
        System.out.println("普通for循环耗时毫秒数:"+(l2-l1));
        System.out.println("foreach循环耗时毫秒数:"+(l3-l2));

        System.out.println("--------------------------------------");

        System.out.println("测试取这100000个元素的id到一个新list");
        long l4 = System.currentTimeMillis();
        List<Integer> idList1 = new ArrayList<Integer>();
        for (int i = 0; i < list.size(); i++) {
            idList1.add(list.get(i).getId());
        }
        long l5 = System.currentTimeMillis();
        List<Integer> idList2 = list.stream().map(p -> p.getId()).collect(Collectors.toList());
        long l6 = System.currentTimeMillis();
        System.out.println("普通for循环耗时毫秒数:"+(l5-l4));
        System.out.println("stream循环耗时毫秒数:"+(l6-l5));
    }

直接给结果:

3.1 循环操作

对应代码片段

long l1 = System.currentTimeMillis();
        for (int i = 0; i < list.size(); i++) {
            list.get(i).setAge(10);
        }
        long l2 = System.currentTimeMillis();
        list.stream().forEach(person -> {
            person.setAge(11);
        });
数据量1w10w100w500w1000w
普通for循环15103247
Stream循环5752607992

分析:
普通循环耗时逐步增加较快,Stream循环增加缓慢
一开始两者速度差距较大,随着数据量增大,速度差距减小

3.2 映射操作

对应代码片段

long l4 = System.currentTimeMillis();
        List<Integer> idList1 = new ArrayList<Integer>();
        for (int i = 0; i < list.size(); i++) {
            idList1.add(list.get(i).getId());
        }
        long l5 = System.currentTimeMillis();
        List<Integer> idList2 = list.stream().map(p -> p.getId()).collect(Collectors.toList());
        long l6 = System.currentTimeMillis();
数据量1w10w100w500w1000w
普通for循环225159294/3091
Stream循环69161613615/224

分析:
数据量小的时候差距并不大,当数据量到了100w的时候,用时发生了变化,以及在1000w的时候两种方法的耗时不固定,这里不知道是不是环境的问题,还是测量方法问题。

3.3 总结

从以上数据有个大概的结论了:

  1. Stream操作比普通的循环操作性能更低一些,但是在数据量一定的情况下,这些差距会减少。
  2. 这些差距其实很小,不能说明Stream性能差,在数据500w的前提下,性能也只有几十毫秒,如果在机器性能高的生产环境下,甚至会消除这部分差距
  3. 对于map映射操作中的耗时问题,需要进一步研究原因,继续测量出更精确的数据,也欢迎大家进行验证。后续会进行源码研究,欢迎大家进行讨论!
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值