1 什么是Stream
Stream是数据渠道,用于操作数据源(集合、数组等)所生成的元素序列。可以执行复杂的查找、过滤和映射数据等操作。Stream API 提供了一种高效且易于使用的处理数据的方式。
注意:
- Stream自己不会存储元素。
- Stream不会改变源对象。相反,他们会返回一个持有结果的新Stream。
- Stream 操作是延迟执行的。这意味着他们会等到需要结果的时候才执行。
2 Stream操作的三个步骤
2.1 创建Stream
- 由Collection创建
Collection接口提供了两个获取流的方法:
default Stream<E> stream(); //返回一个顺序流
default Stream<E> parallelStream(); //返回一个并行流
- 由数组创建
Arrays 的静态方法 stream() 可以获取数组流:
static <T> Stream<T> stream(T[] array);
重载形式,能够处理对应基本类型的数组:
public static IntStream stream(int[] array);
public static LongStream stream(long[] array);
public static DoubleStream stream(double[] array);
- 由值创建
可以使用静态方法 Stream.of(),通过显示值创建一个流。
public static<T> Stream<T> of(T... values);
- 由函数创建无限流
可以使用静态方法Stream.iterate()和Stream.generate():
public static<T> Stream<T> iterate(final T seed, final UnaryOperator<T> f);
public static<T> Stream<T> generate(Supplier<T> s);
//例如
Stream<Integer> stream1 = Stream.iterate(0, (x) -> x + 2);//生成一个从0开始,间隔为2的无限流
Stream<Double> stream2 = Stream.generate(Math::random);//生成一个随机的无限流
2.2 中间操作
多个中间操作可以连接起来形成一个流水线,除非流水线上触发终止操作,否则中间操作不会执行任何的处理。而在终止操作时一次性全部处理,称为“惰性求值”。
- 筛选与切片
Stream filter(Predicate<? super T> predicate);//接收一个断言型参数,从流中过滤某些元素
//例如
List<Employee> emps = Arrays.asList(
new Employee("李四", 59, 6666.66),//姓名,年龄,薪资
new Employee("张三", 18, 9999.99),
new Employee("王五", 28, 3333.33),
new Employee("赵六", 8, 7777.77),
new Employee("田七", 38, 5555.55)
);
emps.stream() //获得流
.filter(e -> e.getAge >= 35); //返回年龄大于或等于35的数据
Stream distinct();//筛选,通过流所生成元素的 hashCode() 和 equals() 去除重复元素
//例如
Stream<Integer> stream = Stream.of(1,2,3,3,3,3);
stream.distinct();//重复的3会被去除,返回的流中只有1,2,3三个元素。
Stream skip(long n);
Stream limit(long maxSize);
类似于SQL语句中的skip和limit,跳过n个元素,不超过maxSize个元素
//例如
emps.stream().skip(2).limit(3);//返回王五、赵六、田七三个元素
- 映射
Stream map(Function f);//接收一个函数作为参数,该函数会被应用到每个元素上,并将其映射成一个新的元素。
emps.stream().map(Emp::getName);//只返回每个对象的名字
Stream flatMap(Function f);//接收一个函数作为参数,将流中的每个值都换成另一个流,然后把所有流连接成一个流
//例如
List<String> list = new ArrayList<>();
list.add("123-456");
list.add("abc-def");
list.add("adsddf-46418");
List result = list.stream().flatMap(str -> Arrays.stream(str.split("-"))).collect(Collectors.toList());
//result的结果是[123, 456, abc, def, adsddf, 46418]
//flatMap接收的函数要求返回一个Stream,把每个stream连成一个stream
//如果使用map,返回的是三个字符串数组,如下
List result = list.stream().map(e -> e.split("-")).collect(Collectors.toList());
- 排序
Stream sorted();//产生一个新流,其中按自然顺序排序(字典排序)
List<String> new = result.sorted().collect(Collectors.toList());
//result: [123, 456, abc, def, adsddf, 46418]
//new: [123, 456, 46418, abc, adsddf, def]
Stream sorted(Comparator<? super T> comparator);//接收一个比较器,可以自定义排序方式。
//例如上面的emp集合,如果age一样就按name排,否则就按age排
emps.stream().sorted((e1,e2) -> {
if(e1.getAge().equals(e2.getAge())){
return e1.getName().compareTo(e2.getName());
}else {
return e1.getAge().compareTo(e2.getAge());
}
})
2.3 终止操作
- 查找与匹配
boolean allMatch(Predicate<? super T> predicate);//检查是否匹配所有元素
//例如
boolean b = emps.stream().allMatch(e -> e.getAge ==28);
//是否所有员工的年龄都是28,这里返回false
boolean anyMatch(Predicate<? super T> predicate);//检查是否至少匹配一个元素
//例如
boolean b = emps.stream().anyMatch(e -> e.getAge ==28);
//是否至少有员工的年龄是28,这里返回true
boolean noneMatch(Predicate<? super T> predicate);//检查是否没有匹配所有元素
//例如
boolean b = emps.stream().noneMatch(e -> e.getAge ==28);
//是否所有有员工的年龄都不是28,这里返回false
Optional findFirst();//返回第一个元素
Optional findAny();//返回当前流中的任意元素
Optional min(Comparator<? super T> comparator);//返回流中最小值
Optional max(Comparator<? super T> comparator);//返回流中最大值
返回的是一个optional对象,需要调用get()方法获取对应的T对象。Optional是java8用来避免空指针的, 下次我们再说。
//例如
Optional<Emp> optional = emps.stream().findFirst();
Emp emp = optional.get();
long count();//返回流中元素总数
- 归约
T reduce(T identity, BinaryOperator accumulator);//可以将流中元素反复结合起来,得到一个值。返回T。
//例如
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);
//起始值为0,将流中元素反复相加,sum = 55
Optional reduce(BinaryOperator accumulator);//返回Optional
Optional<Double> op = emps.stream().map(Employee::getSalary).reduce(Double::sum);
Double sum = op.get();
//因为没有给初始值,返回值可能为空,所以返回的是Optional对象
- 收集
<R, A> R collect(Collector<? super T, A, R> collector);//将流转换为其他形式。接收一个Collector接口的实现,用于给Stream中元素做汇总的方法。
Collectors 实用类提供了很多静态方法,可以方便地创建常见Collector实例,如:
emps.stream().collect(Collectors.toList());//把流中元素收集到List
emps.stream().collect(Collectors.toSet());//把流中元素收集到Set
emps.stream().collect(Collectors.toCollection(ArrayList::new));//把流中元素收集到创建的集合
long count = emps.stream().collect(Collectors.counting());//计算流中元素的个数
int total = emps.stream().collect(Collectors.summingInt(Employee::getSalary));//对流中元素的整数属性求和
double avg= emps.stream().collect(Collectors.averagingInt(Employee::getSalary));//计算流中元素的整数属性的平均值
//收集流中元素的整数属性的统计值。包括count、sum、min、max、average。
IntSummaryStatistics iss = emps.stream().collect(Collectors.summarizingInt(Employee::getSalary));
String str= emps.stream().map(Employee::getName).collect(Collectors.joining());//连接流中每个字符串
//输出"李四张三王五赵六田七"
String str= emps.stream().map(Employee::getName).collect(Collectors.joining(","));//连接流中每个字符串
//输出"李四,张三,王五,赵六,田七"
Optional<Employee> max = list.stream().collect(Collectors.maxBy(comparingInt(Employee::getSalary)));//根据比较器选择最大值
//根据属性值对流进行分组,属性值为键。
Map<Emp.Status, List<Employee>> map = list.stream().collect(Collectors.groupingBy(Employee::getStatus));
//根据true或false进行分区
Map<Boolean,List<Emp>> vd = list.stream().collect(Collectors.partitioningBy(e -> e.getSalary > 5000));
//根据薪资是否大于5000进行分区
3 并行流
3.1 什么是并行流
并行流就是把一个内容分成多个数据块,并用不同的线程分别处理每个数据块的流。并行流的底层其实就是ForkJoin框架的一个实现。
Java8中将并行流进行了优化,我们可以很容易的对数据进行并行操作。Stream API可以声明性地通过parallel()与scqucntial()在并行流与顺序流之间进行切换。
3.2 ForkJoin框架
Fork/Join框架:在必要的情况下,将一个大任务,进行拆分(fork) 成若干个子任务(拆到不能再拆,这里就是指我们制定的拆分的临界值),再将一个个小任务的结果进行join汇总。
Fork/Join采用“工作窃取模式”,当执行新的任务时他可以将其拆分成更小的任务执行,并将小任务加到线程队列中。完成自己的工作而处于空闲的工作线程能够从其他仍处于忙碌(busy)状态的工作线程中窃取等待任务执行,每个工作线程都有自己的工作队列,这是使用双端队列(deque)来实现的。当一个任务划分一个新线程时,它将自己推到deque的头部。当线程的任务队列为空,它将尝试从另一个线程的deque的尾部窃取另一个任务。