Stream(java.util.stream.*)是Java8中处理集合的关键抽象概念,它可以指定你希望对集合进行的操作,可以执行非常复杂的查找、过滤和映射数据等操作。使用Stream API对集合数据进行操作,就类似于使用SQL执行的数据库查询。也可以使用Stream API来进行执行操作。简而言之,Stream API提供了一种高效且易于使用的处理数据的方式。
1、什么是Stream?
流(Stream)到底是什么呢?
是数据渠道,用于操作数据源(集合、数组等)所生成的元素序列。
“集合讲的是数据,流讲的是计算!”
注意:
① Stream自己不会存储元素。
② Stream不会改变源对象(数据源)。相反,它们会返回一个持有结果的新Stream。
③ Stream操作是延迟执行的。这意味着它们会等到需要结果的时候才执行。
2、Stream操作的三个步骤
- 创建Stream
一个数据源(如:数组、集合),获取一个流
- 中间操作
一个中间操作链,对数据源的数据进行处理。
(多个中间操作可以连接起来形成一个流水线,除非流水线上触发终止操作,否则中间操作不会执行任何的处理。而在终止操作时一次性全部处理,称为“惰性求值”)
- 终止操作(终端操作)
一个终止操作,执行中间操作链,并产生结果
3、创建Stream
1.可以通过Collecion系列集合提供的stream()或parallelStream()方法来获取
List<String> list = new ArrayList<String>();
Stream<String> stream1 = list.stream();
2.通过Arrays中的静态方法stream()获取数组流
Employee[] emps = new Employee[20];
Stream<Employee> stream2 = Arrays.stream(emps);
3.通过Stream类中的静态方法of()
Stream<String> stream3 = Stream.of("aa", "bb", "cc");
4.创建无限流
4.1.迭代
Stream<Integer> stream4 = Stream.iterate(0, (x) -> x + 2);
stream4.limit(10).forEach(System.out::println);
4.2.生成
Stream.generate(() -> Math.random()).limit(5).forEach(System.out::println);
4、Stream筛选与切片
筛选与切片:
- filter —接收Lambda表达式,从流中排除某些元素
- limit —截断流,使其元素不超过给定数量
- skip(n) —跳过元素,返回一个扔掉了前n个元素的流。若流中元素不足n个,则返回一个空流。与limit(n)互补
- distinct— 筛选,通过流所生成元素的hashCode()和equals()方法去除重复元素
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.stream.Stream;
import org.junit.Test;
import com.its.java8.lambda.Employee;
/*
* 一、Stream的三个操作步骤是:
*
* 1、创建Stream
* 2、中间操作
* 3、终止操作(终端操作)
*/
public class StreamAPI {
List<Employee> emps = Arrays.asList(
new Employee("张三", 18, 6666.66),
new Employee("李四", 38, 8888.88),
new Employee("王五", 24, 7777.77),
new Employee("赵六", 50, 4444.44),
new Employee("周七", 13, 5555.55),
new Employee("周七", 13, 5555.55));
@Test
public void test4() {
emps.stream()
.filter((e) -> e.getSalary() > 5000)
.skip(2)
.distinct()
.forEach(System.out::println);
}
@Test
public void test3() {
emps.stream().filter((e) -> {
System.out.println("短路!");
return e.getSalary() > 5000;
}).limit(2).forEach(System.out::println);
}
// 内部迭代:迭代操作由Stream API完成
@Test
public void test1() {
// 中间操作:不会执行任何操作
Stream<Employee> s = emps.stream().filter((e) -> {
System.out.println("Stream API的中间操作");
return e.getAge() > 35;
});
// 终止操作:一次性执行全部内容,即“惰性求值”
s.forEach(System.out::println);
}
// 外部迭代
@Test
public void test2() {
Iterator<Employee> it = emps.iterator();
while (it.hasNext()) {
System.out.println(it.next());
}
}
}
5、Stream映射
映射:
- map —接收Lambda表达式,将元素转换成其它形式或提取信息。接收一个函数作为参数,该函数会被应用到每个元素上,并将其映射成一个新的元素。
- flatMap —接收一个函数作为参数,将流中的每个值都转换成另外一个流,然后把所有流连成一个流。
@Test
public void test5() {
List<String> list = Arrays.asList("aaa", "bbb", "ccc", "ddd", "eee");
list.stream().map((str) -> str.toUpperCase()).forEach(System.out::println);
System.out.println("---------------------------");
emps.stream().map(Employee::getName).forEach(System.out::println);
System.out.println("---------------------------");
Stream<Stream<Character>> stream = list.stream().map(StreamAPI2::filterCharacter);
stream.forEach((sm) -> {
sm.forEach(System.out::println);
});
System.out.println("---------------------------");
list.stream().flatMap(StreamAPI2::filterCharacter).forEach(System.out::println);
}
// 将字符串转换成流
public static Stream<Character> filterCharacter(String str) {
List<Character> list = new ArrayList<Character>();
for (Character ch : str.toCharArray()) {
list.add(ch);
}
return list.stream();
}
6、Stream排序
排序:
- sorted —自然排序(Comparable)
- sorted(Comparator com) — 定制排序
@Test
public void test7() {
List<String> list = Arrays.asList("aaa", "bbb", "ccc", "ddd", "eee");
list.stream().sorted().forEach(System.out::println);
System.out.println("----------------------");
emps.stream().sorted((e1, e2) -> {
if (e1.getAge().equals(e2.getAge())) {
return e1.getName().compareTo(e2.getName());
} else {
return -e1.getAge().compareTo(e2.getAge());
}
}).forEach(System.out::println);
}
7、Stream查找与匹配
查找与匹配:
- allMatch —检查是否匹配所有元素
- anyMatch —检查是否至少匹配一个元素
- noneMatch —检查是否没有匹配所有元素
- findFirst —返回第一个元素
- findAny —返回当前流中的任意元素
- count —返回六种元素的总个数
- max —返回流中的最大值
- min— 返回流中的最小值
员工类:Employee.java
public class Employee {
private Integer id;
private String name;
private Integer age;
private Double salary;
private Status status; // 员工状态
public Employee() {}
public Employee(Integer id) {
this.id = id;
}
public Employee(Integer id, Integer age) {
this.id = id;
this.age = age;
}
public Employee(String name, Integer age, Double salary) {
this.name = name;
this.age = age;
this.salary = salary;
}
public Employee(String name, Integer age, Double salary, Status status) {
this.name = name;
this.age = age;
this.salary = salary;
this.status = status;
}
// 省略setter和getter方法…
// 省略hashCode和equals方法…
// 省略toString方法…
// 定义枚举,表示员工状态
public enum Status {
FREE, BUSY, VOCATION;
}
}
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import org.junit.Test;
import com.its.java8.lambda.Employee;
import com.its.java8.lambda.Employee.Status;
/*
* 终止操作
*/
public class StreamAPI {
List<Employee> emps = Arrays.asList(
new Employee("张三", 18, 6666.66, Status.FREE),
new Employee("李四", 38, 8888.88, Status.BUSY),
new Employee("王五", 24, 7777.77, Status.VOCATION),
new Employee("赵六", 50, 4444.44, Status.FREE),
new Employee("周七", 13, 5555.55, Status.BUSY));
@Test
public void test2() {
// 获取公司中的所有员工总数量
long count = emps.stream().count();
System.out.println(count);
// 查找工资最高的员工信息
Optional<Employee> op = emps.stream().max((e1, e2) -> Double.compare(e1.getSalary(), e2.getSalary()));
System.out.println(op.get());
// 查找所有员工中的最低工资
Optional<Double> op2 = emps.stream().map(Employee::getSalary).min(Double::compareTo);
System.out.println(op2);
}
@Test
public void test1() {
boolean b1 = emps.stream().allMatch((e) -> e.getStatus().equals(Status.BUSY));
System.out.println(b1); // false
boolean b2 = emps.stream().anyMatch((e) -> e.getStatus().equals(Status.BUSY));
System.out.println(b2); // true
boolean b3 = emps.stream().noneMatch((e) -> e.getStatus().equals(Status.BUSY));
System.out.println(b3); // false
Optional<Employee> op = emps.stream().sorted((e1, e2) -> Double.compare(e1.getSalary(), e2.getSalary())).findFirst();
System.out.println(op.get());
// Employee [id=null, name=赵六, age=50, salary=4444.44]
Optional<Employee> op2 = emps.stream().filter((e)-> e.getStatus().equals(Status.FREE)).findAny();
System.out.println(op2.get());
// Employee [id=null, name=张三, age=18, salary=6666.66]
}
}
8、Stream归约与收集
归约:
- reduce(T identity, BinaryOperator) / reduce(BinaryOperator) — 可以将流中元素反复结合起来,得到一个值
@Test
public void test3() {
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
Integer sum = list.stream().reduce(0, (x, y) -> x + y);
System.out.println(sum);
System.out.println("--------------------");
Optional<Double> op = emps.stream().map(Employee::getSalary).reduce(Double::sum);
System.out.println(op.get());
}
收集:
- collect— 将流转换为其它形式。接收一个Collector接口的实现,用于给Stream中元素做汇总的方法
@Test
public void test4() {
List<String> list = emps.stream().map(Employee::getName).collect(Collectors.toList());
list.forEach(System.out::println);
System.out.println("-------------------");
Set<String> set = emps.stream().map(Employee::getName).collect(Collectors.toSet());
set.forEach(System.out::println);
System.out.println("-------------------");
HashSet<String> hs = emps.stream().map(Employee::getName).collect(Collectors.toCollection(HashSet::new));
hs.forEach(System.out::println);
}
@Test
public void test5() {
// 获取员工总数
Long count = emps.stream().collect(Collectors.counting());
System.out.println(count);
System.out.println("-------------------");
// 获取工资平均值
Double avgSalary = emps.stream().collect(Collectors.averagingDouble(Employee::getSalary));
System.out.println(avgSalary);
System.out.println("-------------------");
// 工资总和
Double sumSalary = emps.stream().collect(Collectors.summingDouble(Employee::getSalary));
System.out.println(sumSalary);
// 获取工资最高的员工信息
Optional<Employee> max = emps.stream().collect(Collectors.maxBy((e1, e2) -> Double.compare(e1.getSalary(), e2.getSalary())));
System.out.println(max.get());
// 最低工资
Optional<Double> min = emps.stream().map(Employee::getSalary).collect(Collectors.minBy(Double::compareTo));
System.out.println(min.get());
}
// 分组
@Test
public void test6() {
Map<Status, List<Employee>> map = emps.stream().collect(Collectors.groupingBy(Employee::getStatus));
System.out.println(map);
}
// 多级分组
@Test
public void test7() {
Map<Status, Map<String, List<Employee>>> map = emps.stream().
collect(Collectors.groupingBy(Employee::getStatus, Collectors.groupingBy((e) -> {
if (((Employee) e).getAge() <= 35) {
return "青年";
} else if (((Employee) e).getAge() <= 50) {
return "中年";
} else {
return "老年";
}
})));
System.out.println(map);
}
// 分区
@Test
public void test8(){
Map<Boolean, List<Employee>> map = emps.stream().collect(Collectors.partitioningBy((e) -> e.getSalary() > 8000));
System.out.println(map);
}
@Test
public void test9() {
DoubleSummaryStatistics dss = emps.stream().collect(Collectors.summarizingDouble(Employee::getSalary));
System.out.println(dss.getAverage());
System.out.println(dss.getCount());
System.out.println(dss.getMax());
System.out.println(dss.getMin());
System.out.println(dss.getSum());
}
@Test
public void test10() {
String str = emps.stream().map(Employee::getName).collect(Collectors.joining(","));
System.out.println(str);
}
9、并行流与顺序流
并行流就是把一个内容分成多个数据块,并用不同的线程分别处理每个数据块的流。
Java8中将并行进行了优化,我们可以很容易的对数据进行并行操作。Stream API可以声明性的通过paralle()与sequential()在并行流与顺序流之间进行切换。
9.1、了解Fork/Join框架
Fork/Join框架:是Java7提供的一个用于并行执行任务的框架,就是在必要的情况下,将一个大任务,拆分(fork)成若干个小任务(拆到不可再拆时),最终将一个个的小任务运算的结果进行join汇总得到大任务结果的框架。
9.2、工作窃取算法
Fork/Join框架采用“工作窃取”模式(work-stealing):当执行新的任务时它可以将其拆分分成更小的任务执行。并将小任务加到线程队列中执行,然后再从一个随机线程的队列中窃取一个任务并把它放在自己的线程队列中执行。
9.3、为什么要使用工作窃取算法?
假如我们需要做一个比较大的任务,我们可以把这个任务分割为若干互不依赖的子任务,为了减少线程间的竞争,于是把这些子任务分别放到不同的队列里,并为每个队列创建一个单独的线程来执行队列里的任务,线程和队列一一对应。比如A线程负责处理A队列里的任务,但是有的线程会先把自己队列里的任务执行完,而其它线程对应的队列里还有任务等待处理,干完活的线程与其等着,不如去帮其它线程干活,于是它就去其它线程的队列里窃取一个任务来执行。而在这时它们会访问同一个队列,所以为了减少窃取任务线程和被窃取任务线程之间的竞争,通常会使用双端队列,被窃取任务线程永远从双端队列的头部拿执行任务,而窃取任务的线程永远从双端队列的尾部拿执行任务。
工作窃取算法的优点是充分利用线程进行并行计算,并减少了线程间的竞争,其缺点是在某些情况下还是存在竞争,比如双端队列里只有一个任务时,并且消耗了更多的系统资源,比如创建多个线程和多个双端队列。
9.4、Fork/Join框架与传统线程池的区别:
相对于一般的线程池实现,Fork/Join框架的优势体现在对其中包含的任务的处理方式上。在一般的线程池中,如果一个线程正在执行的任务由于某些原因无法继续运行,那么该线程会处于等待状态。而在fork/join框架实现中,如果某个子问题由于等待另外一个子问题的完成而无法继续运行,那么该处理子问题的线程会主动寻找其它尚未运行的子问题来执行,这种方式减少了线程的等待时间,提高了性能。
9.5、Fork/Join实现API(Java7)
Fork/Join使用两个类来完成任务:
ForkJoinTask:需要使用ForkJoin框架,必须首先创建一个ForkJoin任务,它提供在任务中fork()和join()操作的机制,通常情况下不需要直接继承ForkJoinTask类,而只需要继承它的子类,ForkJoin框架提供了两个子类:
- RecursiveAction:用于没有返回结果的任务。
- RecursiveTask:用于有返回结果的任务。
ForkJoinPool:ForkJoinTask需要通过ForkJoinPool来执行,任务分割出的子任务会添加到当前工作线程所维护的双端队列中,进入队列的头部。当一个工作线程的队列里暂时没有任务时,它会随机从其它工作线程的队列的尾部获取一个任务来执行。
Java8之前的并行流实现:
ForkJoinCalculate.java
import java.util.concurrent.RecursiveTask;
/**
* 了解Fork/Join框架
*
*/
public class ForkJoinCalculate extends RecursiveTask<Long> {
private static final long serialVersionUID = -779618140117304453L;
private long start;
private long end;
// 临界值
private static final long THRESHOLD = 10000;
public ForkJoinCalculate(long start, long end) {
this.start = start;
this.end = end;
}
@Override
protected Long compute() {
long length = end - start;
if (length <= THRESHOLD) {
long sum = 0;
for (long i = start; i < end; i++) {
sum += i;
}
return sum;
} else {
long middle = (start + end) / 2;
ForkJoinCalculate left = new ForkJoinCalculate(start, middle);
left.fork(); // 拆分子任务,同时压入线程队列
ForkJoinCalculate right = new ForkJoinCalculate(middle + 1, end);
right.fork();
return left.join() + right.join();
}
}
}
测试:计算1-10亿数值累加,以及所耗费的时间
import java.time.Duration;
import java.time.Instant;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.ForkJoinTask;
import java.util.stream.LongStream;
import org.junit.Test;
public class TestForkJoin {
/*
* fork/join
*/
@Test
public void test1() {
Instant start = Instant.now();
ForkJoinPool pool = new ForkJoinPool();
ForkJoinTask<Long> task = new ForkJoinCalculate(0, 10000000000L);
Long sum = pool.invoke(task);
System.out.println(sum);
Instant end = Instant.now();
System.out.println("耗费时间为:" + Duration.between(start, end).toMillis() + "毫秒");
}
/*
* 普通for循环
*/
@Test
public void test2() {
long sum = 0L;
Instant start = Instant.now();
for (long i = 0; i < 10000000000L; i++) {
sum += i;
}
System.out.println(sum);
Instant end = Instant.now();
System.out.println("耗费时间为:" + Duration.between(start, end).toMillis() + "毫秒");
}
/*
* java8并行流实现
*/
@Test
public void test3() {
Instant start = Instant.now();
long sum = LongStream.rangeClosed(0, 1000000000L).parallel().reduce(0, Long::sum);
Instant end = Instant.now();
System.out.println(sum);
System.out.println("耗费时间为:" + Duration.between(start, end).toMillis() + "毫秒"); // 440-414-426
}
}