Java8引入了一个全新的流式API:Stream API,它位于java.util.stream
包中。
这个Stream不同于java.io
的InputStream
和OutputStream
,它代表的是任意Java对象的序列。
Stream和List也不一样,List存储的每个元素都是已经存储在内存中的某个Java对象,而Stream输出的元素可能并没有预先存储在内存中,而是实时计算出来的。
Stream可以表示无限序列,元素有可能已经全部存储在内存中,也有可能是根据需要实时计算出来的。一个Stream可以轻易地转换为另一个Stream,而不是修改原Stream本身。真正的计算通常发生在最后结果的获取,也就是惰性计算。
最简单的方式是直接用Stream.of()
public class StreamDemo {
public static void main(String[] args) {
Stream<Integer> integerStream=Stream.of(1,2,3,4,5);
integerStream.forEach(System.out::print);
}
}
再就是基于一个数组或者Collection
public class StreamDemo {
public static void main(String[] args) {
Integer[] array = {1, 2, 3, 4, 5};
List<Integer> list = new ArrayList<>();
for (int i = 0; i < 5; i++) {
list.add(i + 1);
}
Stream<Integer> stream1 = Arrays.stream(array);
Stream<Integer> stream2 = list.stream();
stream1.forEach(System.out::print);
stream2.forEach(System.out::print);
}
}
还可以通过Stream.generate()方法,它需要传入一个Supplier对象
public class StreamDemo {
public static void main(String[] args) {
//基于Supplier创建的Stream会不断调用Supplier.get()方法来不断产生下一个元素,
// 这种Stream保存的不是元素,而是算法,它可以用来表示无限序列。
Stream<Integer> integerStream = Stream.generate(new Supplier<Integer>() {
int n = 1;
@Override
public Integer get() {
return n++;
}
});
//无限序列必须先变成有限序列
integerStream.limit(20).forEach(System.out::println);
}
}
上述代码我们用一个Supplier
模拟了一个无限序列(当然受int范围限制不是真的无限大)。如果用List表示,即便在int范围内,也会占用巨大的内存,而Stream几乎不占用空间,因为每个元素都是实时计算出来的,用的时候再算
另外,Files类的lines()方法可以把一个文件变成一个Stream,每个元素代表文件的一行内容:
public class StreamDemo {
public static void main(String[] args) throws IOException {
Stream<String> lines = Files.lines(Paths.get("D:\\test.txt"));
lines.forEach(System.out::println);
}
}
此方法对于按行遍历文本文件十分有用。
因为Java的泛型不支持基本类型,所以我们无法用Stream
这样的类型。为了保存int,只能使用Stream
,但这样会产生频繁的装箱、拆箱操作,会降低代码执行效率。为了提高效率,Java标准库提供了IntStream
、LongStream
和DoubleStream
这三种使用基本类型的Stream,例如:
public class StreamDemo {
public static void main(String[] args) throws IOException {
IntStream intStream=IntStream.generate(new IntSupplier(){
int n=1;
@Override
public int getAsInt() {
return n++;
}
});
intStream.limit(20).forEach(System.out::println);
}
}
Stream.map()
是Stream最常用的一个转换方法,它把一个Stream转换为另一个Stream。
例如:
通过map对字符集合去空格、变小写:
/**
* 通过map对字符集合去空格、变小写
**/
public class StreamDemo {
public static void main(String[] args) throws IOException {
List<String> list=new ArrayList<>();
list.add(" apple");
list.add("Banana");
list.add("ORANGE");
Stream<String> stream = list.stream();
stream.map(String::toLowerCase)
.map(String::trim)
.forEach(System.out::println);
}
}
去除日期当中的空格并输出该日是本年的第几天:
/**
* 去除日期当中的空格并输出该日是本年的第几天
*/
public class StreamDemo {
public static void main(String[] args) throws IOException {
String[] times = new String[]{"2020 - 04 - 01", "2022 - 04 - 14"};
Stream<String> timesStream = Arrays.stream(times);
timesStream.map(t->t.replaceAll(" ",""))
.map(LocalDate::parse)
.map(LocalDate::getDayOfYear)
.forEach(System.out::println);
}
}
将一组序列转换为它的立方:
/**
* 将一组序列转换为它的立方
*/
public class StreamDemo {
public static void main(String[] args) {
IntStream intStream = IntStream.generate(new IntSupplier() {
int n = 1;
@Override
public int getAsInt() {
return n++;
}
});
IntStream intStream1 = intStream.map(n -> n * n * n);
intStream1.limit(5).forEach(System.out::println);
}
}
filter()操作就是对一个Stream的所有元素根据条件进行过滤,满足条件的元素就构成了一个新的Stream
例如:
判断元素是否是奇数,过滤掉偶数,只剩下奇数:
/**
* 判断元素是否是奇数,过滤掉偶数,只剩下奇数
*/
public class StreamDemo {
public static void main(String[] args) {
IntStream intStream = IntStream.generate(new IntSupplier() {
int n = 1;
@Override
public int getAsInt() {
return n++;
}
});
IntStream intStream1 = intStream.filter(n -> n % 2 != 0);
intStream1.limit(5).forEach(System.out::println);
}
}
从一组给定的LocalDate中过滤掉工作日,以便得到休息日:
/**
* 从一组给定的LocalDate中过滤掉工作日,以便得到休息日
*/
public class StreamDemo {
public static void main(String[] args) {
Stream<LocalDate> stream = Stream.generate(new Supplier<LocalDate>() {
LocalDate start = LocalDate.of(2022, 4, 1);
int n = 0;
@Override
public LocalDate get() {
return start.plusDays(n++);
}
});
stream.limit(31)
.filter(d -> d.getDayOfWeek() == DayOfWeek.SATURDAY
|| d.getDayOfWeek() == DayOfWeek.SUNDAY)
.forEach(System.out::println);
}
}
reduce()则是Stream的一个聚合方法,它可以把一个Stream的所有元素按照聚合函数聚合成一个结果。
例如:
返回累加的结果
/**
* 返回累加的结果
*/
public class StreamDemo {
public static void main(String[] args) {
List<Integer> list=new ArrayList<>();
for (int i = 0; i < 10; i++) {
list.add(i+1);
}
Stream<Integer> stream = list.stream();
Integer acc = stream.reduce(0, (sum, i) -> sum + i);
System.out.println(acc);
}
}
以上代码可能不太好理解,我们用另一种等价的形式改写一下
Stream
int sum = 0;
for (n : stream) {
sum = (sum, n) -> sum + n;
}
举个例子,从配置文件读取数据并准换成Map:
public class StreamDemo {
public static void main(String[] args) throws IOException {
//读取配置文件
Stream<String> lines = Files.lines(Paths.get("D:\\properties.yaml"), Charset.forName("UTF-8"));
//将读取到由每一行组成的Stream转成单个Map组成的Stream,然后聚合
Map<String, String> result = lines.map(line -> {
Map<String, String> map = new HashMap<>();
String[] split = line.split("=");
map.put(split[0], split[1]);
return map;
}).reduce(new HashMap<String, String>(), (map, it) -> {
map.putAll(it);
return map;
});
System.out.println(result);
}
}
执行以下代码:
public static void main(String[] args) throws IOException, InterruptedException {
IntStream stream=IntStream.generate(new IntSupplier() {
int n=0;
@Override
public int getAsInt() {
return n++;
}
});
IntStream intStream = stream.limit(100000000).map(n -> n * n);
// List list = stream.limit(100000000).mapToObj(i -> i).collect(Collectors.toList());
Thread.sleep(10000000);
}
会发现没有太高的内存占用,也没有CPU占用
因为转换操作只是保存了转换规则,无论我们对一个Stream转换多少次,都不会有任何实际计算发生。
但如果运行以下代码:
public class StreamDemo {
public static void main(String[] args) throws IOException, InterruptedException {
IntStream stream=IntStream.generate(new IntSupplier() {
int n=0;
@Override
public int getAsInt() {
return n++;
}
});
// IntStream intStream = stream.limit(100000000).map(n -> n * n);
List<Integer> list = stream.limit(100000000).mapToObj(i -> i).collect(Collectors.toList());
Thread.sleep(10000000);
}
}
会发现占用了大量内存和CPU
这是因为大量的数据存储在了List当中,占用了大量内存
public static void main(String[] args) throws IOException, InterruptedException {
IntStream stream = IntStream.generate(new IntSupplier() {
int n = 0;
@Override
public int getAsInt() {
return n++;
}
});
int[] array = stream.limit(100).toArray();
System.out.println(Arrays.toString(array));
}
public static void main(String[] args) throws IOException, InterruptedException {
Stream<Integer> stream = Stream.of(1, 2, 3, 4);
List<Integer> list = stream.collect(Collectors.toList());
System.out.println(list);
}
调用collect()并传入Collectors.toList()对象,通过类似reduce()的操作,把每个元素添加到ArrayList当中。
类似的,collect(Collectors.toSet())可以把Stream的每个元素收集到Set中。
Stream<Integer> stream = Stream.of(1, 2, 3, 4);
Set<Integer> set = stream.collect(Collectors.toSet());
System.out.println(set);
public class StreamDemo {
public static void main(String[] args) throws IOException, InterruptedException {
Stream<String> stream = Stream.of("a:A", "b:B");
Map<String, String> map = stream.collect(Collectors.toMap(
s -> s.substring(0, s.indexOf(':')),
s -> s.substring(s.indexOf(':') + 1)
));
System.out.println(map);
}
}
如此一来,第四节将配置文件转换成map的d代码就可以简化为
public class StreamDemo {
public static void main(String[] args) throws IOException, InterruptedException {
Stream<String> lines = Files.lines(Paths.get("D:\\properties.yaml"), Charset.forName("UTF-8"));
Map<String, String> map = lines.collect(Collectors.toMap(
line -> line.substring(0, line.indexOf('=')), // 配置文件里的key
line -> line.substring(line.indexOf('=') + 1) //配置文件里的value
));
System.out.println(map);
}
}
分组输出使用Collectors.groupingBy()
,它需要提供两个函数:一个是分组的依据,第二个是分组的结果
public class StreamDemo {
public static void main(String[] args) throws IOException, InterruptedException {
Stream<String> stream
= Stream.of("Apple", "Banana", "Blackberry", "Coconut", "Avocado", "Cherry", "Apricots");
Map<String, List<String>> result = stream.collect(Collectors.groupingBy(
s -> s.substring(0, 1), // 表示按照首字母分组
Collectors.toList() //表示表示输出为List
));
System.out.println(result);
}
}
Stream<Integer> stream=Stream.of(6,8,6,8,7,2,3,3,9,9,4,6,1);
List<Integer> list = stream.sorted().collect(Collectors.toList());
System.out.println(list);
此方法要求Stream的每个元素必须实现Comparable接口。
可自定义Comparable,传入sorted()。
以下sorted((i1, i2) -> i2.compareTo(i1))
实现降序排序:
Stream<Integer> stream = Stream.of(6, 8, 6, 8, 7, 2, 3, 3, 9, 9, 4, 6, 1);
List<Integer> list = stream
.sorted((i1, i2) -> i2.compareTo(i1))
.collect(Collectors.toList());
System.out.println(list);
Stream<Integer> stream = Stream.of(6, 8, 6, 8, 7, 2, 3, 3, 9, 9, 4, 6, 1);
List<Integer> list = stream
.distinct() // 去重
.collect(Collectors.toList());
System.out.println(list);