共计 5085 个字符,预计需要花费 13 分钟才能阅读完成。
- stream 的操作品种
- ①两头操作
- ②终端操作
- stream 的特点
- ①只能遍历一次:
- ②采纳外部迭代的形式:
- stream 绝对于 Collection 的长处
- stream 和 iterator 迭代的效率比拟低
- 1. 映射解决测试
- 2. 过滤解决测试
- 3. 天然排序测试
- 4. 归约统计测试
- 5. 字符串拼接测试
- 6. 混合操作测试
Stream 是 Java SE 8 类库中新增的要害形象,它被定义于 java.util.stream(这个包里有若干流类型:Stream<T> 代表对象援用流,此外还有一系列特化流,如 IntStream,LongStream,DoubleStream 等。
Java 8 引入的的 Stream 次要用于取代局部 Collection 的操作,每个流代表一个值序列,流提供一系列罕用的汇集操作,能够便捷的在它下面进行各种运算。汇合类库也提供了便捷的形式使咱们能够以操作流的形式应用汇合、数组以及其它数据结构;
stream 的操作品种
①两头操作
- 当数据源中的数据上了流水线后,这个过程对数据进行的所有操作都称为“两头操作”;
- 两头操作依然会返回一个流对象,因而多个两头操作能够串连起来造成一个流水线;
- stream 提供了多种类型的两头操作,如 filter、distinct、map、sorted 等等;
②终端操作
- 当所有的两头操作实现后,若要将数据从流水线上拿下来,则须要执行终端操作;
- stream 对于终端操作,能够间接提供一个两头操作的后果,或者将后果转换为特定的 collection、array、String 等;
stream 的特点
①只能遍历一次:
数据流从一头获取数据源,在流水线上顺次对元素进行操作,当元素通过流水线,便无奈再对其进行操作,能够从新在数据源获取一个新的数据流进行操作;
②采纳外部迭代的形式:
对 Collection 进行解决,个别会应用 Iterator 遍历器的遍历形式,这是一种内部迭代;
而对于解决 Stream,只有申明解决形式,处理过程由流对象自行实现,这是一种外部迭代,对于大量数据的迭代解决中,外部迭代比内部迭代要更加高效;
stream 绝对于 Collection 的长处
- 无存储: 流并不存储值;流的元素源自数据源(可能是某个数据结构、生成函数或 I / O 通道等等),通过一系列计算步骤失去;
- 函数式格调: 对流的操作会产生一个后果,但流的数据源不会被批改;
- 感性求值: 少数流操作(包含过滤、映射、排序以及去重)都能够以惰性形式实现。这使得咱们能够用一遍遍历实现整个流水线操作,并能够用短路操作提供更高效的实现;
- 无需上界: 不少问题都能够被表白为有限流(infinite stream):用户不停地读取流直到称心的后果呈现为止(比如说,枚举 完满数 这个操作能够被表白为在所有整数上进行过滤);汇合是无限的,但流能够表白为无线流;
- 代码简练: 对于一些 collection 的迭代解决操作,应用 stream 编写能够非常简洁,如果应用传统的 collection 迭代操作,代码可能非常啰嗦,可读性也会比拟蹩脚;
stream 和 iterator 迭代的效率比拟
好了,下面 stream 的长处吹了那么多,stream 函数式的写法是很难受,那么 steam 的效率到底怎么呢?
先说论断:
- 传统 iterator (for-loop) 比 stream(JDK8) 迭代性能要高,尤其在小数据量的状况下;
- 在多核情景下,对于大数据量的解决,parallel stream 能够有比 iterator 更高的迭代解决效率;
我别离对一个随机数列 List(数量从 10 到 10000000)进行映射、过滤、排序、规约统计、字符串转化场景下,对应用 stream 和 iterator 实现的运行效率进行了统计,测试代码 基准测试代码链接
测试环境如下:
System:Ubuntu 16.04 xenial
CPU:Intel Core i7-8550U
RAM:16GB
JDK version:1.8.0_151
JVM:HotSpot(TM) 64-Bit Server VM (build 25.151-b12, mixed mode)
JVM Settings:
-Xms1024m
-Xmx6144m
-XX:MaxMetaspaceSize=512m
-XX:ReservedCodeCacheSize=1024m
-XX:+UseConcMarkSweepGC
-XX:SoftRefLRUPolicyMSPerMB=100
1. 映射解决测试
把一个随机数列(List<Integer>)中的每一个元素自增 1 后,从新组装为一个新的 List<Integer>,测试的随机数列容量从 10 – 10000000,跑 10 次取均匀工夫;
//stream
List<Integer> result = list.stream()
.mapToInt(x -> x)
.map(x -> ++x)
.boxed()
.collect(Collectors.toCollection(ArrayList::new));
//iterator
List<Integer> result = new ArrayList<>();
for(Integer e : list){result.add(++e);
}
//parallel stream
List<Integer> result = list.parallelStream()
.mapToInt(x -> x)
.map(x -> ++x)
.boxed()
.collect(Collectors.toCollection(ArrayList::new));
2. 过滤解决测试
取出一个随机数列(List<Integer>)中的大于 200 的元素,并组装为一个新的 List<Integer>,测试的随机数列容量从 10 – 10000000,跑 10 次取均匀工夫;
//stream
List<Integer> result = list.stream()
.mapToInt(x -> x)
.filter(x -> x > 200)
.boxed()
.collect(Collectors.toCollection(ArrayList::new));
//iterator
List<Integer> result = new ArrayList<>(list.size());
for(Integer e : list){if(e > 200){result.add(e);
}
}
//parallel stream
List<Integer> result = list.parallelStream()
.mapToInt(x -> x)
.filter(x -> x > 200)
.boxed()
.collect(Collectors.toCollection(ArrayList::new));
3. 天然排序测试
对一个随机数列(List<Integer>)进行天然排序,并组装为一个新的 List<Integer>,iterator 应用的是 Collections # sort API(应用归并排序算法实现),测试的随机数列容量从 10 – 10000000,跑 10 次取均匀工夫;
//stream
List<Integer> result = list.stream()
.mapToInt(x->x)
.sorted()
.boxed()
.collect(Collectors.toCollection(ArrayList::new));
//iterator
List<Integer> result = new ArrayList<>(list);
Collections.sort(result);
//parallel stream
List<Integer> result = list.parallelStream()
.mapToInt(x->x)
.sorted()
.boxed()
.collect(Collectors.toCollection(ArrayList::new));
4. 归约统计测试
获取一个随机数列(List<Integer>)的最大值,测试的随机数列容量从 10 – 10000000,跑 10 次取均匀工夫;
//stream
int max = list.stream()
.mapToInt(x -> x)
.max()
.getAsInt();
//iterator
int max = -1;
for(Integer e : list){if(e > max){max = e;}
}
//parallel stream
int max = list.parallelStream()
.mapToInt(x -> x)
.max()
.getAsInt();
5. 字符串拼接测试
获取一个随机数列(List<Integer>)各个元素应用“,”分隔的字符串,测试的随机数列容量从 10 – 10000000,跑 10 次取均匀工夫;
//stream
String result = list.stream().map(String::valueOf).collect(Collectors.joining(","));
//iterator
StringBuilder builder = new StringBuilder();
for(Integer e : list){builder.append(e).append(",");
}
String result = builder.length() == 0 ? "" : builder.substring(0,builder.length() - 1);
//parallel stream
String result = list.stream().map(String::valueOf).collect(Collectors.joining(","));
6. 混合操作测试
对一个随机数列(List<Integer>)进行去空值,除重,映射,过滤,并组装为一个新的 List<Integer>,测试的随机数列容量从 10 – 10000000,跑 10 次取均匀工夫;
//stream
List<Integer> result = list.stream()
.filter(Objects::nonNull)
.mapToInt(x -> x + 1)
.filter(x -> x > 200)
.distinct()
.boxed()
.collect(Collectors.toCollection(ArrayList::new));
//iterator
HashSet<Integer> set = new HashSet<>(list.size());
for(Integer e : list){if(e != null && e > 200){set.add(e + 1);
}
}
List<Integer> result = new ArrayList<>(set);
//parallel stream
List<Integer> result = list.parallelStream()
.filter(Objects::nonNull)
.mapToInt(x -> x + 1)
.filter(x -> x > 200)
.distinct()
.boxed()
.collect(Collectors.toCollection(ArrayList::new));
试验后果总结
从以上的试验来看,能够总结处以下几点:
- 在少低数据量的解决场景中(size<=1000),stream 的解决效率是不如传统的 iterator 内部迭代器处理速度快的,然而实际上这些解决工作自身运行工夫都低于毫秒,这点效率的差距对一般业务简直没有影响,反而 stream 能够使得代码更加简洁;
- 在大数据量(szie>10000)时,stream 的解决效率会高于 iterator,特地是应用了并行流,在 cpu 恰好将线程调配到多个外围的条件下(当然 parallel stream 底层应用的是 JVM 的 ForkJoinPool,这货色调配线程自身就很玄学),能够达到一个很高的运行效率,然而理论一般业务个别不会有须要迭代高于 10000 次的计算;
- Parallel Stream 受引 CPU 环境影响很大,当没调配到多个 cpu 外围时,加上援用 forkJoinPool 的开销,运行效率可能还不如一般的 Stream;
应用 Stream 的倡议
- 简略的迭代逻辑,能够间接应用 iterator,对于有多步解决的迭代逻辑,能够应用 stream,损失一点简直没有的效率,换来代码的高可读性是值得的;
- 单核 cpu 环境,不举荐应用 parallel stream,在多核 cpu 且有大数据量的条件下,举荐应用 paralle stream;
- stream 中含有装箱类型,在进行两头操作之前,最恶化成对应的数值流,缩小因为频繁的拆箱、装箱造成的性能损失;