目录
- 简介
- 用法
- 例子
- 注意点
一. 简介
流是Java8引入的一个新特性,提供了对集合元素一系列便捷的操作,可以用很少的代码实现复杂的功能。
流有两大类,分别是对象流(Stream),基本数据流(IntStream、LongStream、DoubleStream)。
二.用法
流的使用通常为三个步骤:①创建流 ②描述流 ③求值
创建流
通过Stream静态工厂方法来创建,Stream类提供了以下方法创建流。
- of:
Streamstream = Stream.of(1, 2, 3, 4);
IntStream intStream = IntStream.of(new int[]{1, 2, 3}); - generator:生成一个无限长度的Stream
可以看作是个工厂,每次返回一个给定类型的对象。
Streamgenerate = Stream.generate(Math::random); - iterate:生成无限长度的Stream
其元素的生成是重复对给定的种子值(seed)调用用户指定函数生成的
Streamstream = Stream.iterate(1, element -> element + 1); - empty():生成空流
Stream.empty(); - 通过文件生成流:File.lines()
Streamlines = Files.lines(Paths.get("data.txt")); - 最常用的 通过Collection子类获取Stream
Listlist = new ArrayList<>();
Streamstream = list.stream(); - 通过Arrays.stream()封装数据生成流,也很常用
IntStream intStream = Arrays.stream(new int[]{1, 2, 3}); - 比较少用的 CharSequence 接口方法 .chars()
String str = "hello world";
IntStream intStream = str.chars();
描述流
创建好流之后,就可以描述流了。更准确的说法是,声明流的中间操作。
中间操作比较常用的有以下几种(返回值为Stream的均为中间操作)
- .filter(man -> man.getAge() > 18) 过滤器
- .map(Man::getName) 获取字段,map操作可以执行多次,将数据转为我们期望的
- .mapToInt 转为数值流,这个和sum这些组合使用可以省去装箱成本
- .limit(3) 截断流,前三个
- .distinct() 去重
- .sorted() 排序
- .skip(3) 丢弃前三个
- .flatMap(Arrays::stream) 合并流
求值
与中间操作相对应的是终端操作,也就是我们的第三步,求值。
常用的终端操作有以下几种(返回值不为Stream的为终端操作)
- .collect(toList()); 结果转为List格式
- .collect(toSet()); 结构转为Set格式,自带distinct效果
- .count(); 计数
- .findfirst(); 找第一个
- .findAny(); 找任何一个
- .forEach(System.out::println) 为每个元素进行的操作
- .min 获取最小值
- .max 获取最大值
说明
流是使用了内部迭代而不是外部迭代
内部迭代使用了惰性求值,只有在需要结果的那一刻才进行求值。
所有的中间操作(只描述流)都是惰性求值,因为只返回了Stream。
三.例子
// 用户类
public class User {
private Integer id;
private String name;
private Integer age;
// GET SET Constructor ..
}
// 初始化数据
List userList = new ArrayList() {
/** */
private static final long serialVersionUID = 1L;
{
add(new User(1, "Luffy", 17));
add(new User(2, "Zoro", 19));
add(new User(3, "Nami", 18));
add(new User(4, "Usopp", 17));
add(new User(5, "Sanji", 19));
add(new User(6, "Chopper", 15));
add(new User(7, "Robin", 28));
add(new User(8, "FRANKY", 34));
add(new User(9, "BROOK", 88));
}
};
// 例子
@Test
public void test1() {
// 获取所有年龄大于17的用户
List users = userList.stream()
.filter(user -> user.getAge() > 17)
.collect(Collectors.toList());
System.out.println(users);
}
// 静态导入Collectors后会更简洁。
// import static java.util.stream.Collectors.*;
@Test
public void test2() {
// 根据年龄是否大于17岁对集合进行分块
Map> users = userList.stream()
.collect(partitioningBy(u -> u.getAge() > 17));
System.out.println(users);
}
@Test
public void test3() {
// 按照年龄,将用户分组
Map> users = userList.stream()
.collect(groupingBy(User::getAge));
System.out.println(users);
}
@Test
public void test4() {
// 获取所有用户的名称
List names = userList.stream()
.map(User::getName)
.collect(toList());
System.out.println(names);
}
@Test
public void test5() {
// 获取年龄最大的用户(只返回一个)
User user = userList.stream()
.max((u1, u2) -> {
return u1.getAge() > u2.getAge() ? 1 : -1;
})
.get();
System.out.println(user);
}
@Test
public void test6() {
// 获取用户年龄总和
int ageSum = userList.stream()
.map(User::getAge)
.reduce(0, (acc, element) -> {
acc += element;
return acc;
});
System.out.println(ageSum);
ageSum = userList.stream()
.map(User::getAge)
.reduce(0, Integer::sum);
System.out.println(ageSum);
ageSum = userList.stream()
.collect(summingInt(User::getAge));
System.out.println(ageSum);
}
@Test
public void test7() {
// 获取组成用户姓名的字母
Set set = userList.stream()
.map(User::getName)
.map(name -> name.split(""))
.flatMap(Arrays::stream)
.collect(toSet());
System.out.println(set);
}
四.注意点
如何进行调试
// 可以利用peek查看流中的数据状态
userList.stream()
.peek(System.out::println)
.map(u -> u.getName())
.peek(System.out::println)
.collect(toList());
使用并行还是串行
// 将流改为并行流十分简单,只需要将stream方法改为parallelStream
Map> users = userList.parallelStream()
.collect(groupingBy(User::getAge));
// 或是使用 parallel() 声明为并行
Stream.iterate(0L, i -> i + 1)
.parallel()
.limit(10)
.reduce(0L, Long::sum);
Java8流的并行属于数据并行,即将数据分为几部分,利用多核CPU对每块数据执行运算,最终汇总数据,得到答案。底层是使用了fork/join框架,fork递归分解问题,然后每段并行执行,最终join合并结果。 因此,使用并行流需要考虑以下几点:
- 数据量 : 将问题分解之后并行化处理,再将结果合并会带来额外的开销。
因此数据足够大,每个数据处理管道花费的时间足够多时,并行化处理才有意义 - 将数据对半分解的难易程度 :
ArrayList、数组、IntStream.range,这些数据结构支持随机读取,也就是说能轻而易举地被任意分解。
LinkedList,对半分解太难。需要o(n) 时间复杂度 - 核的数量 : 单核的机器是没必要并行化的