1. Stream
Java8中有两大最为重要的改变。第一个是Lambda表达式的出现。另外一个则是Stream API。我们可以通过Stream API对集合进行各种操作。且通过Stream API对集合进行操作是非常方便高效的。
1.1 Stream是什么
Stream是加工数据的流水线。
Stream本身不会存数据,也不会改变源对象。Stream操作是延迟执行的,也就是说Stream会等到需要结果的时候才执行。
1.2 Stream三板斧
A. 创建Stream:根据一个数据源,创建出一个操作该数据源的流水线
B. 中间操作:一个中间操作链,指定对数据源中的数据如何处理
C. 终止操作:一个终止操作,执行中间操作链,并产生结果
1.2.1 创建Stream
@Test
public void test() {
通过Collections.stream()来根据集合创建Stream
List<String> list = new ArrayList<>(Arrays.asList("Andy Eason G.E.M".split(" ")));
Stream<String> stream = list.stream();
通过Arrays.stream()来根据数组创建Stream
User[] users = new User[10];
Stream<User> stream2 = Arrays.stream(users);
通过Stream.of()来根据直接提供的数据创建Stream
Stream<String> stream3 = Stream.of("foo bar baz".split(" "));
通过“迭代”创建无限Stream
Stream<Integer> stream4 = Stream.iterate(0, x -> x + 2);
通过“生成”创建无限Stream
Stream<Integer> stream5 = Stream.generate(() -> new Random().nextInt(100));
Stream targetStream = stream5;
targetStream.limit(10).forEach(System.out::println);
}
1.2.3 中间操作
多个中间操作可以连接起来形成一个流水线,除非流水线上触发了终止操作,否则中间操作是不会有实际的执行的。在终止操作时一次性全部处理,称为“惰性求值”。
测试数据
@Before
public void before() {
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
User user = new User(1, "Andy", LocalDate.parse("1961-09-27", formatter), 3000d);
User user2 = new User(2, "Eason", LocalDate.parse("1974-07-27", formatter), 2500d);
User user3 = new User(3, "G.E.M", LocalDate.parse("1991-08-16", formatter), 4000d);
蔡徐坤", LocalDate.parse("1998-08-02", formatter), 1200d);
关晓彤", LocalDate.parse("1997-09-17", formatter), 6000d);
userList = new ArrayList<>(Arrays.asList(user, user2, user3, user4, user5));
}
筛选
filter:从流中过滤出指定元素, 筛选的特点是,Stream中的数据类型以前是什么,筛选完以后的结果也是什么类型
@Test
public void test() {
Stream<User> s = userList.stream();
Stream<User> s2 = s.filter(u -> u.getBalance() > 3000);
// print false
System.out.println(s == s2);
s2.forEach(System.out::println);
}
切片
limit:指定从流中获取数据的个数
@Test
public void test() {
userList.stream()
.filter(u -> u.getBalance() > 1000d)
.limit(3)
.forEach(System.out::println);
}
skip:跳过满足条件的前n个元素,若流中元素不足n个,则返回一个空流
@Test
public void test() {
userList.stream()
.filter(u -> u.getBalance() > 1000d)
.skip(2)
.forEach(System.out::println);
}
distinct:消除重复元素 (distinct利用hashCode和equals来判断元素是否重复,所以我们要让目标类重写Object的hashCode和equals方法,而如果在目标类上使用了lombok的@Data注解,则lombok会帮我们重写hashCode和equals方法)
@Test
public void test() {
userList.stream()
.filter(u -> u.getBalance() > 3000)
.distinct()
.forEach(System.out::println);
}
映射
map 映射的特点是, 流中的数据以前是什么类型,经过映射以后,类型可能会变成另外一种类型
@Test
public void test() {
userList.stream()
.map(u -> u.getName())
.forEach(System.out::println);
}
以上代码可以进一步简化为以下代码:
@Test
public void test() {
userList.stream()
.map(User::getName)
.forEach(System.out::println);
}
flatMap
有如下场景,出现了嵌套Stream对象的情况,即一个Stream的元素又是一个Stream:
public class Foo {
public static Stream<Character> f1(String str) {
List<Character> list = new ArrayList<>();
for (int i = 0; i < str.length(); i++) {
list.add(str.charAt(i));
}
return list.stream();
}
}
遍历Stream中的每个元素,而每个元素又是一个Stream,需要再对这个Stream元素进行遍历:
@Test
public void test() {
List<String> list = Arrays.asList("Andy Eason G.E.M S.H.E Ekin".split(" "));
Stream<Stream<Character>> stream = list.stream()
.map(Foo::f1);
stream.forEach(s -> s.forEach(System.out::println));
}
使用flatMap可以简化以上的代码:
@Test
public void test() {
List<String> list = Arrays.asList("Andy Eason G.E.M S.H.E Ekin".split(" "));
Stream<Character> stream = list.stream()
.flatMap(Foo::f1);
stream.forEach(System.out::println);
}
排序
sorted() 自然排序
@Test
public void test() {
List<String> list = Arrays.asList("Eason Andy Bob Dracula Clark".split(" "));
list.stream().sorted()
.forEach(System.out::println);
}
sorted(Comparator) 定制排序
@Test
public void test() {
List<String> list = Arrays.asList("Eason Andy Bob Dracula Clark ".split(" "));
list.stream().sorted((e1, e2) -> Integer.compare(e1.length(), e2.length()))
.forEach(System.out::println);
}
终止操作:查找与匹配
allMatch 是否所有元素都匹配条件
@Test
public void test() {
boolean b = userList.stream().allMatch(e -> e.getBalance() > 1000);
System.out.println("b = " + b);
boolean b2 = userList.stream().allMatch(e -> e.getBalance() > 3000);
System.out.println("b2 = " + b2);
}
anyMatch 是否有至少一个元素匹配条件
@Test
public void test() {
boolean b = userList.stream().anyMatch(e -> e.getBalance() < 1000);
System.out.println("b = " + b);
boolean b2 = userList.stream().anyMatch(e -> e.getBalance() > 3000);
System.out.println("b2 = " + b2);
}
noneMatch 是否所有元素都不匹配条件
@Test
public void test() {
boolean b = userList.stream().noneMatch(e -> e.getBalance() < 1000);
System.out.println("b = " + b);
boolean b2 = userList.stream().noneMatch(e -> e.getBalance() < 3000);
System.out.println("b2 = " + b2);
}
findFirst 返回第一个元素
@Test
public void test() {
userList.stream().filter(u -> u.getBalance() < 3000)
.findFirst().ifPresent(System.out::println);
}
findAny 返回当前流中的任意一个元素(在同步stream中也总是返回第一个,只有在异步stream中才具有随机性)
@Test
public void test() {
userList.stream().filter(u -> u.getBalance() < 3000)
.findAny().ifPresent(System.out::println);
}
count 返回流中元素的总个数
@Test
public void test() {
long count = userList.stream().filter(u -> u.getBalance() < 3000)
.count();
System.out.println("count = " + count);
}
max 返回流中的最大值
@Test
public void test() {
userList.stream()
.max((u, u2) -> u.getId().compareTo(u2.getId()))
.ifPresent(System.out::println);
}
min 返回流中的最小值
@Test
public void test() {
userList.stream().filter(u -> u.getBalance() < 3000)
.min((u, u2) -> u.getId().compareTo(u2.getId()))
.ifPresent(System.out::println);
}
forEach
@Test
public void test() {
userList.stream().forEach(System.out::println);
上下代码,效果等价============================");
userList.forEach(System.out::println);
}
终止操作:归约与收集
reduce 将流中元素反复结合起来,得到一个值
@Test
public void test() {
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
Integer sum = list.stream().reduce(0, (a, b) -> a + b);
System.out.println("sum = " + sum);
}
举一反三
@Test
public void test() {
Double sum = userList.stream()
.map(User::getBalance)
.reduce(0.0, Double::sum);
System.out.println("sum = " + sum);
}
注意,如果调用reduce的时候,没有传入第一个参数,则计算的值有可能为空,所以在没有为reduce传入第一个参数的情况下,reduce返回的是Optional:
@Test
public void test() {
userList.stream()
.map(User::getBalance)
.reduce(Double::sum)
.ifPresent(System.out::println);
}
collect 收集:将流转换为其他形式。接收一个Collector接口的实现,用于给Stream中的元素做汇总。
收集所有用户的名字到一个Set集合中:
@Test
public void test() {
Set<String> set = userList.stream()
等价于 u -> u.getName();
.collect(Collectors.toSet());
等价于 name -> System.out.println(name)
}
收集所有用户的名字到一个List集合中:
@Test
public void test() {
List<String> list = userList.stream()
等价于 u -> u.getName();
.collect(Collectors.toList());
等价于 name -> System.out.println(name)
}
将结果存入Map中:
@Testpublic void test() {
Map<Integer, User> map = userList.stream().filter(u -> u.getBalance() > 3000).collect(Collectors.toMap(u -> u.getId(), u -> u));
map.forEach((k,v) -> {
System.out.println(k + " --- " + v);
});
}
如果我们想把收集到的数据存入一个特殊类型的集合中呢?比如存入到一个LinkedHashSet。这时候可以使用Collectors.toCollection(Suplier):
@Test
public void test() {
LinkedHashSet<String> set = userList.stream()
.map(User::getName)
.collect(Collectors.toCollection(() -> new LinkedHashSet<>()));
set.forEach(System.out::println);
}
计算收集到的数据的总数:
@Test
public void test() {
Long count = userList.stream()
.map(User::getName)
.collect(Collectors.counting());
System.out.println("count = " + count);
}
计算收集到的数字类型数据的平均值
@Test
public void test() {
Double avg = userList.stream()
// .map(User::getBalance)
注意下面这一行的averagingDouble方法中的参数:"User::getBalance"代替了上一行的 map(User::getBalance)
.collect(Collectors.averagingDouble(User::getBalance));
System.out.println("avg = " + avg);
}
计算收集到的数字类型数据的总和
@Test
public void test() {
Double sum = userList.stream()
.collect(Collectors.summingDouble(User::getBalance));
System.out.println("sum = " + sum);
}
计算收集到的数据的最大值
@Test
public void test() {
Optional<User> max = userList.stream()
.collect(Collectors.maxBy((u, u2) -> u.getName().compareTo(u2.getName())));
max.ifPresent(System.out::println);
}
计算收集到的数据的最小值
@Test
public void test() {
Optional<User> min = userList.stream()
.collect(Collectors.minBy((u, u2) -> u.getName().compareTo(u2.getName())));
min.ifPresent(System.out::println);
}
分组
先收集用户的名字,再按照用户名字的长度分组,对所有用户名字进行分组:
@Test
public void test() {
Map<Integer, List<String>> map = userList.stream()
.map(User::getName)
.collect(Collectors.groupingBy(name -> name.length()));
map.forEach((k, v) -> System.out.println(k + ": " + v));
}
直接按照用户名字的长度,对用户对象本身进行分组(而不是对用户的名字分组)
@Test
public void test() {
Map<Integer, List<User>> map = userList.stream()
.collect(Collectors.groupingBy(u -> u.getName().length()));
map.forEach((k,v) -> System.out.println(k + ": " + v));
}
多级分组,与SQL中,按多列分组的情况一样:
@Test
public void test() {
Map<Integer, Map<String, List<User>>> map = userList.stream()
.collect(Collectors.groupingBy(u -> u.getName().length(), Collectors.groupingBy(u -> {
Double balance = u.getBalance();
if (balance < 1000) {
潘";
} else if (balance < 3000) {
温饱";
} else if (balance < 6000) {
小康";
} else {
土豪";
}
})));
map.forEach((k, v) -> System.out.println(k + ": " + v));
}
按照用户的余额分组,余额大于2500的是一组,小于等于2500的是另一组:
@Test
public void test() {
Map<Boolean, List<User>> map = userList.stream()
.collect(Collectors.groupingBy(u -> u.getBalance() > 2500));
map.forEach((k, v) -> System.out.println(k + ": " + v));
}
当按照true和false来进行分组的时候,就可以直接使用分区完成,所以可以改写上面代码如下:
@Test
public void test() {
Map<Boolean, List<User>> map = userList.stream()
.collect(Collectors.partitioningBy(u -> u.getBalance() <= 2500));
map.forEach((k,v) -> System.out.println(k + ": " + v));
}
一次性计算多个聚合值:
@Test
public void test() {
DoubleSummaryStatistics statistics = userList.stream()
.collect(Collectors.summarizingDouble(User::getBalance));
System.out.println(statistics.getMax());
System.out.println(statistics.getMin());
System.out.println(statistics.getCount());
System.out.println(statistics.getAverage());
System.out.println(statistics.getSum());
}
连接
将所有用户的名字用"---"拼接起来:
@Test
public void test() {
String str = userList.stream()
.map(User::getName)
.collect(Collectors.joining("---"));
System.out.println(str);
}
@Test
public void test() {
String str = userList.stream()
.map(User::getName)
.collect(Collectors.joining("," , "(" , ")"));
System.out.println(str);
}
练习:
1. 给定一个数字列表,返回一个由每个数字的平方构成的List
@Test
public void test() {
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5)
.stream()
.map(e -> e * e)
.collect(Collectors.toList());
list.forEach(System.out::println);
}
2.给定一个User列表, 统计其中有多少个User,必须使用map和reduce来实现
@Test
public void test() {
userList.stream()
.map(u -> 1)
等价于 (a, b) -> a + b
.ifPresent(System.out::println);
}
2. 并行流与顺序流
2.1 Stream串行流和并行流切换
之前讲解的Stream都是顺序流,也就是单线程的。Stream也可以切换为并行流,并行流是一个把要处理的内容分成多个块,且用不同的线程分别处理每个块的流。通过Stream API中的parallel()与sequential()在并行流与顺序流之间进行切换。
@Test
public void test() {
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);
List<Integer> resultList = list.stream()
.parallel()
.map(x -> x * 10)
.collect(Collectors.toList());
resultList.forEach(System.out::println);
}
在并行的情况下,findAny的结果就具有随机性了
@Testpublic void test() {
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
Optional<Integer> any = list.stream()
.parallel()
.filter(n -> n > 1)
.findAny();
System.out.println(any.isPresent() ? any.get() : null);
}
2.2 Fork/Join框架(了解)
fork/join框架是ExecutorService接口的一个实现,可以帮助开发人员充分利用多核处理器的优势,编写出并行执行的程序,提高应用程序的性能;设计的目的是为了处理那些可以被递归拆分的任务。
Fork/Join 框架 与 线程池的区别
1. .采用 “工作窃取” 模式 (work-stealing)。当执行新的任务时它可以将其拆分成更小的任务执行,并将小任务加到线程队列中,当没有任务执行时,再从一个随机线程的队列中偷一个并把它放在自己的队列中
2. 相对于一般的线程池实现 ,fork/join 框架的优势体现在对其中包含的任务的处理方式上,在一般的线程池中,如果一个线程正在执行的任务由于某些原因无法继续运行,那么该线程会处于等待状态。而在fork/join 框架实现中,如果某个子问题由于等待另外一个子问题的完成而无法继续运行。那么处理该子问题的线程会主动寻找其他尚未运行的子问题(窃取过来)来执行,这种方式减少了线程的等待时间,提高了性能!
3. Fork/Join执行大任务时,是阻塞的。
需求:计算1加到1_000_000_000的值
Fork/Join 步骤一:
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.RecursiveTask;
class MyForkJoin extends RecursiveTask<Long> {
@Override
protected Long compute() {
System.out.println("MyForkJoin.compute");
return 5L;
}
}
public class AppTest {
public static void main(String[] args) {
ForkJoinPool pool = new ForkJoinPool();
MyForkJoin mfj = new MyForkJoin();
System.out.println("A");
Long result = pool.invoke(mfj);
System.out.println("B");
System.out.println("result = " + result);
}
}
Fork/Join 步骤二:
import java.time.Duration;
import java.time.Instant;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.RecursiveTask;
class MyForkJoin extends RecursiveTask<Long> {
private Long begin;
private Long end;
public MyForkJoin(Long begin, Long end) {
this.begin = begin;
this.end = end;
}
@Override
protected Long compute() {
Long sum = 0L;
for (Long i = begin; i <= end; i++) {
sum += i;
}
return sum;
}
}
public class AppTest {
public static void main(String[] args) {
ForkJoinPool pool = new ForkJoinPool();
MyForkJoin mfj = new MyForkJoin(1L, 1000000000L);
Instant start = Instant.now();
Long result = pool.invoke(mfj);
long elapse = Duration.between(start, Instant.now()).toMillis();
System.out.println("Task took " + elapse + " ms");
}
}
Fork/Join 步骤三
import java.time.Duration;
import java.time.Instant;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.RecursiveTask;
class MyForkJoin extends RecursiveTask<Long> {
private Long begin;
private Long end;
private static final Long THURSHOLD = 10000000L;
public MyForkJoin(Long begin, Long end) {
this.begin = begin;
this.end = end;
}
@Override
protected Long compute() {
Long length = end - begin;
递推转折点
Long sum = 0L;
for (Long i = begin; i <= end; i++) {
sum += i;
}
return sum;
继续递推
Long middle = (begin + end) / 2;
MyForkJoin left = new MyForkJoin(begin, middle);
拆分出小任务,并把小任务压入某个线程的任务队列
MyForkJoin right = new MyForkJoin(middle + 1, end);
拆分出小任务,并把小任务压入某个线程的任务队列
return left.join() + right.join();
}
}
}
public class AppTest {
public static void main(String[] args) {
ForkJoinPool pool = new ForkJoinPool();
MyForkJoin mfj = new MyForkJoin(1L, 1000000000L);
Instant start = Instant.now();
Long result = pool.invoke(mfj);
long elapse = Duration.between(start, Instant.now()).toMillis();
System.out.println("Task took " + elapse + " ms");
}
}