1. Stream简单介绍
1.1 概述
- Stream是 Java 8新增加的类,用来补充集合类,它提供了过滤、映射、排序、聚合等方式来对集合进行数据操作。
- Steam类在java.util包里,并且将steam方法加入了Collection接口里面。
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()));
}
代码执行结果如下:
- 可见使用Stream进行数据操作非常方便,以上的实现方式,大家可以尝试使用普通的for循环进行实现,看下需要多少行代码。
- 筛选的结果也是大于18岁的person。映射也成功活去了所有person的id。
- 并且验证了使用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);
});
数据量 | 1w | 10w | 100w | 500w | 1000w |
---|---|---|---|---|---|
普通for循环 | 1 | 5 | 10 | 32 | 47 |
Stream循环 | 57 | 52 | 60 | 79 | 92 |
分析:
普通循环耗时逐步增加较快,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();
数据量 | 1w | 10w | 100w | 500w | 1000w |
---|---|---|---|---|---|
普通for循环 | 2 | 2 | 51 | 59 | 294/3091 |
Stream循环 | 6 | 9 | 16 | 161 | 3615/224 |
分析:
数据量小的时候差距并不大,当数据量到了100w的时候,用时发生了变化,以及在1000w的时候两种方法的耗时不固定,这里不知道是不是环境的问题,还是测量方法问题。
3.3 总结
从以上数据有个大概的结论了:
- Stream操作比普通的循环操作性能更低一些,但是在数据量一定的情况下,这些差距会减少。
- 这些差距其实很小,不能说明Stream性能差,在数据500w的前提下,性能也只有几十毫秒,如果在机器性能高的生产环境下,甚至会消除这部分差距
- 对于map映射操作中的耗时问题,需要进一步研究原因,继续测量出更精确的数据,也欢迎大家进行验证。后续会进行源码研究,欢迎大家进行讨论!