把Stream流学透了你也能写出简洁高效的代码,快来点击进来看看吧(建议收藏)

Integer arr[] = {1,2,3,5,6,8};

Stream.of(arr)

.filter(i->i>0)

.forEach(System.out::println);

System.out.println(“---------”);

// 为了提高程序代码的效率,我们可以先将流中Integer数据转换为int数据,然后再操作

IntStream intStream = Stream.of(arr)

.mapToInt(Integer::intValue);

intStream.filter(i->i>3)

.forEach(System.out::println);

}

4.15 concat

如果有两个流,希望合并成为一个流,那么可以使用Stream接口的静态方法concat

public static Stream concat(Stream<? extends T> a, Stream<? extends T> b) {

Objects.requireNonNull(a);

Objects.requireNonNull(b);

@SuppressWarnings(“unchecked”)

Spliterator split = new Streams.ConcatSpliterator.OfRef<>(

(Spliterator) a.spliterator(), (Spliterator) b.spliterator());

Stream stream = StreamSupport.stream(split, a.isParallel() || b.isParallel());

return stream.onClose(Streams.composedClose(a, b));

}

使用:

public static void main(String[] args) {

Stream stream1 = Stream.of(“a”,“b”,“c”);

Stream stream2 = Stream.of(“x”, “y”, “z”);

// 通过concat方法将两个流合并为一个新的流

Stream.concat(stream1,stream2).forEach(System.out::println);

}

4.16 综合案例

定义两个集合,然后在集合中存储多个用户名称。然后完成如下的操作:

  1. 第一个队伍只保留姓名长度为3的成员

  2. 第一个队伍筛选之后只要前3个人

  3. 第二个队伍只要姓张的成员

  4. 第二个队伍筛选之后不要前两个人

  5. 将两个队伍合并为一个队伍

  6. 根据姓名创建Person对象

  7. 打印整个队伍的Person信息

package com.bobo.jdk.stream;

import com.bobo.jdk.lambda.domain.Person;

import java.util.Arrays;

import java.util.List;

import java.util.stream.Stream;

public class StreamTest21Demo {

/**

    1. 第一个队伍只保留姓名长度为3的成员
    1. 第一个队伍筛选之后只要前3个人
    1. 第二个队伍只要姓张的成员
    1. 第二个队伍筛选之后不要前两个人
    1. 将两个队伍合并为一个队伍
    1. 根据姓名创建Person对象
    1. 打印整个队伍的Person信息
  • @param args

*/

public static void main(String[] args) {

List list1 = Arrays.asList(“迪丽热巴”, “宋远桥”, “苏星河”, “老子”, “庄子”, “孙子”, “洪七 公”);

List list2 = Arrays.asList(“古力娜扎”, “张无忌”, “张三丰”, “赵丽颖”, “张二狗”, “张天爱”, “张三”);

// 1. 第一个队伍只保留姓名长度为3的成员

// 2. 第一个队伍筛选之后只要前3个人

Stream stream1 = list1.stream().filter(s -> s.length() == 3).limit(3);

// 3. 第二个队伍只要姓张的成员

// 4. 第二个队伍筛选之后不要前两个人

Stream stream2 = list2.stream().filter(s -> s.startsWith(“张”)).skip(2);

// 5. 将两个队伍合并为一个队伍

// 6. 根据姓名创建Person对象

// 7. 打印整个队伍的Person信息

Stream.concat(stream1,stream2)

//.map(n-> new Person(n))

.map(Person::new)

.forEach(System.out::println);

}

}

输出结果:

Person{name=‘宋远桥’, age=null, height=null}

Person{name=‘苏星河’, age=null, height=null}

Person{name=‘张二狗’, age=null, height=null}

Person{name=‘张天爱’, age=null, height=null}

Person{name=‘张三’, age=null, height=null}

5.Stream结果收集


5.1 结果收集到集合中

/**

  • Stream结果收集

  • 收集到集合中

*/

@Test

public void test01(){

// Stream stream = Stream.of(“aa”, “bb”, “cc”);

List list = Stream.of(“aa”, “bb”, “cc”,“aa”)

.collect(Collectors.toList());

System.out.println(list);

// 收集到 Set集合中

Set set = Stream.of(“aa”, “bb”, “cc”, “aa”)

.collect(Collectors.toSet());

System.out.println(set);

// 如果需要获取的类型为具体的实现,比如:ArrayList HashSet

ArrayList arrayList = Stream.of(“aa”, “bb”, “cc”, “aa”)

//.collect(Collectors.toCollection(() -> new ArrayList<>()));

.collect(Collectors.toCollection(ArrayList::new));

System.out.println(arrayList);

HashSet hashSet = Stream.of(“aa”, “bb”, “cc”, “aa”)

.collect(Collectors.toCollection(HashSet::new));

System.out.println(hashSet);

}

输出:

[aa, bb, cc, aa]

[aa, bb, cc]

[aa, bb, cc, aa]

[aa, bb, cc]

5.2 结果收集到数组中

Stream中提供了toArray方法来将结果放到一个数组中,返回值类型是Object[],如果我们要指定返回的类型,那么可以使用另一个重载的toArray(IntFunction f)方法

/**

  • Stream结果收集到数组中

*/

@Test

public void test02(){

Object[] objects = Stream.of(“aa”, “bb”, “cc”, “aa”)

.toArray(); // 返回的数组中的元素是 Object类型

System.out.println(Arrays.toString(objects));

// 如果我们需要指定返回的数组中的元素类型

String[] strings = Stream.of(“aa”, “bb”, “cc”, “aa”)

.toArray(String[]::new);

System.out.println(Arrays.toString(strings));

}

5.3 对流中的数据做聚合计算

当我们使用Stream流处理数据后,可以像数据库的聚合函数一样对某个字段进行操作,比如获得最大值,最小值,求和,平均值,统计数量。

/**

  • Stream流中数据的聚合计算

*/

@Test

public void test03(){

// 获取年龄的最大值

Optional maxAge = Stream.of(

new Person(“张三”, 18)

, new Person(“李四”, 22)

, new Person(“张三”, 13)

, new Person(“王五”, 15)

, new Person(“张三”, 19)

).collect(Collectors.maxBy((p1, p2) -> p1.getAge() - p2.getAge()));

System.out.println(“最大年龄:” + maxAge.get());

// 获取年龄的最小值

Optional minAge = Stream.of(

new Person(“张三”, 18)

, new Person(“李四”, 22)

, new Person(“张三”, 13)

, new Person(“王五”, 15)

, new Person(“张三”, 19)

).collect(Collectors.minBy((p1, p2) -> p1.getAge() - p2.getAge()));

System.out.println(“最新年龄:” + minAge.get());

// 求所有人的年龄之和

Integer sumAge = Stream.of(

new Person(“张三”, 18)

, new Person(“李四”, 22)

, new Person(“张三”, 13)

, new Person(“王五”, 15)

, new Person(“张三”, 19)

)

//.collect(Collectors.summingInt(s -> s.getAge()))

.collect(Collectors.summingInt(Person::getAge))

;

System.out.println(“年龄总和:” + sumAge);

// 年龄的平均值

Double avgAge = Stream.of(

new Person(“张三”, 18)

, new Person(“李四”, 22)

, new Person(“张三”, 13)

, new Person(“王五”, 15)

, new Person(“张三”, 19)

).collect(Collectors.averagingInt(Person::getAge));

System.out.println(“年龄的平均值:” + avgAge);

// 统计数量

Long count = Stream.of(

new Person(“张三”, 18)

, new Person(“李四”, 22)

, new Person(“张三”, 13)

, new Person(“王五”, 15)

, new Person(“张三”, 19)

).filter(p->p.getAge() > 18)

.collect(Collectors.counting());

System.out.println(“满足条件的记录数:” + count);

}

5.4 对流中数据做分组操作

当我们使用Stream流处理数据后,可以根据某个属性将数据分组

/**

  • 分组计算

*/

@Test

public void test04(){

// 根据账号对数据进行分组

Map<String, List> map1 = Stream.of(

new Person(“张三”, 18, 175)

, new Person(“李四”, 22, 177)

, new Person(“张三”, 14, 165)

, new Person(“李四”, 15, 166)

, new Person(“张三”, 19, 182)

).collect(Collectors.groupingBy(Person::getName));

map1.forEach((k,v)-> System.out.println(“k=” + k +“\t”+ “v=” + v));

System.out.println(“-----------”);

// 根据年龄分组 如果大于等于18 成年否则未成年

Map<String, List> map2 = Stream.of(

new Person(“张三”, 18, 175)

, new Person(“李四”, 22, 177)

, new Person(“张三”, 14, 165)

, new Person(“李四”, 15, 166)

, new Person(“张三”, 19, 182)

).collect(Collectors.groupingBy(p -> p.getAge() >= 18 ? “成年” : “未成年”));

map2.forEach((k,v)-> System.out.println(“k=” + k +“\t”+ “v=” + v));

}

输出结果:

k=李四 v=[Person{name=‘李四’, age=22, height=177}, Person{name=‘李四’, age=15, height=166}]

k=张三 v=[Person{name=‘张三’, age=18, height=175}, Person{name=‘张三’, age=14, height=165}, Person{name=‘张三’, age=19, height=182}]


k=未成年 v=[Person{name=‘张三’, age=14, height=165}, Person{name=‘李四’, age=15, height=166}]

k=成年 v=[Person{name=‘张三’, age=18, height=175}, Person{name=‘李四’, age=22, height=177}, Person{name=‘张三’, age=19, height=182}]

多级分组: 先根据name分组然后根据年龄分组

/**

  • 分组计算–多级分组

*/

@Test

public void test05(){

// 先根据name分组,然后根据age(成年和未成年)分组

Map<String,Map<Object,List>> map = Stream.of(

new Person(“张三”, 18, 175)

, new Person(“李四”, 22, 177)

, new Person(“张三”, 14, 165)

, new Person(“李四”, 15, 166)

, new Person(“张三”, 19, 182)

).collect(Collectors.groupingBy(

Person::getName

,Collectors.groupingBy(p->p.getAge()>=18?“成年”:“未成年”

)

));

map.forEach((k,v)->{

System.out.println(k);

v.forEach((k1,v1)->{

System.out.println(“\t”+k1 + “=” + v1);

});

});

}

输出结果:

李四

未成年=[Person{name=‘李四’, age=15, height=166}]

成年=[Person{name=‘李四’, age=22, height=177}]

张三

未成年=[Person{name=‘张三’, age=14, height=165}]

成年=[Person{name=‘张三’, age=18, height=175}, Person{name=‘张三’, age=19, height=182}]

5.5 对流中的数据做分区操作

Collectors.partitioningBy会根据值是否为true,把集合中的数据分割为两个列表,一个true列表,一个false列表

在这里插入图片描述

/**

  • 分区操作

*/

@Test

public void test06(){

Map<Boolean, List> map = Stream.of(

new Person(“张三”, 18, 175)

, new Person(“李四”, 22, 177)

, new Person(“张三”, 14, 165)

, new Person(“李四”, 15, 166)

, new Person(“张三”, 19, 182)

).collect(Collectors.partitioningBy(p -> p.getAge() > 18));

map.forEach((k,v)-> System.out.println(k+“\t” + v));

}

输出结果:

false [Person{name=‘张三’, age=18, height=175}, Person{name=‘张三’, age=14, height=165}, Person{name=‘李四’, age=15, height=166}]

true [Person{name=‘李四’, age=22, height=177}, Person{name=‘张三’, age=19, height=182}]

5.6 对流中的数据做拼接

Collectors.joining会根据指定的连接符,将所有的元素连接成一个字符串

/**

  • 对流中的数据做拼接操作

*/

@Test

public void test07(){

String s1 = Stream.of(

new Person(“张三”, 18, 175)

, new Person(“李四”, 22, 177)

, new Person(“张三”, 14, 165)

, new Person(“李四”, 15, 166)

, new Person(“张三”, 19, 182)

).map(Person::getName)

.collect(Collectors.joining());

// 张三李四张三李四张三

System.out.println(s1);

String s2 = Stream.of(

new Person(“张三”, 18, 175)

, new Person(“李四”, 22, 177)

, new Person(“张三”, 14, 165)

, new Person(“李四”, 15, 166)

, new Person(“张三”, 19, 182)

).map(Person::getName)

.collect(Collectors.joining(“_”));

// 张三_李四_张三_李四_张三

System.out.println(s2);

String s3 = Stream.of(

new Person(“张三”, 18, 175)

, new Person(“李四”, 22, 177)

, new Person(“张三”, 14, 165)

, new Person(“李四”, 15, 166)

, new Person(“张三”, 19, 182)

).map(Person::getName)

.collect(Collectors.joining(“_”, “###”, “$$$”));

// ###张三_李四_张三_李四_张三$$$

System.out.println(s3);

}

6. 并行的Stream流


6.1 串行的Stream流

我们前面使用的Stream流都是串行,也就是在一个线程上面执行。

/**

  • 串行流

*/

@Test

public void test01(){

Stream.of(5,6,8,3,1,6)

.filter(s->{

System.out.println(Thread.currentThread() + “” + s);

return s > 3;

}).count();

}

输出:

Thread[main,5,main]5

Thread[main,5,main]6

Thread[main,5,main]8

Thread[main,5,main]3

Thread[main,5,main]1

Thread[main,5,main]6

6.2 并行流

parallelStream其实就是一个并行执行的流,它通过默认的ForkJoinPool,可以提高多线程任务的速度。

6.2.1 获取并行流

我们可以通过两种方式来获取并行流。

  1. 通过List接口中的parallelStream方法来获取

  2. 通过已有的串行流转换为并行流(parallel)

实现:

/**

  • 获取并行流的两种方式

*/

@Test

public void test02(){

List list = new ArrayList<>();

// 通过List 接口 直接获取并行流

Stream integerStream = list.parallelStream();

// 将已有的串行流转换为并行流

Stream parallel = Stream.of(1, 2, 3).parallel();

}

6.2.2 并行流操作

/**

  • 并行流操作

*/

@Test

public void test03(){

Stream.of(1,4,2,6,1,5,9)

.parallel() // 将流转换为并发流,Stream处理的时候就会通过多线程处理

.filter(s->{

System.out.println(Thread.currentThread() + " s=" +s);

return s > 2;

}).count();

}

效果

Thread[main,5,main] s=1

Thread[ForkJoinPool.commonPool-worker-2,5,main] s=9

Thread[ForkJoinPool.commonPool-worker-6,5,main] s=6

Thread[ForkJoinPool.commonPool-worker-13,5,main] s=2

Thread[ForkJoinPool.commonPool-worker-9,5,main] s=4

Thread[ForkJoinPool.commonPool-worker-4,5,main] s=5

Thread[ForkJoinPool.commonPool-worker-11,5,main] s=1

6.3 并行流和串行流对比

我们通过for循环,串行Stream流,并行Stream流来对500000000亿个数字求和。来看消耗时间

package com.bobo.jdk.res;

import org.junit.After;

import org.junit.Before;

import org.junit.Test;

import java.util.stream.LongStream;

public class Test03 {

private static long times = 500000000;

private long start;

@Before

public void befor(){

start = System.currentTimeMillis();

}

@After

public void end(){

long end = System.currentTimeMillis();

System.out.println(“消耗时间:” + (end - start));

}

/**

  • 普通for循环 消耗时间:138

*/

@Test

public void test01(){

System.out.println(“普通for循环:”);

long res = 0;

for (int i = 0; i < times; i++) {

res += i;

}

}

/**

  • 串行流处理

  • 消耗时间:203

*/

@Test

public void test02(){

System.out.println(“串行流:serialStream”);

LongStream.rangeClosed(0,times)

.reduce(0,Long::sum);

}

/**

  • 并行流处理 消耗时间:84

*/

@Test

public void test03(){

LongStream.rangeClosed(0,times)

.parallel()

.reduce(0,Long::sum);

}

}

通过案例我们可以看到parallelStream的效率是最高的。

Stream并行处理的过程会分而治之,也就是将一个大的任务切分成了多个小任务,这表示每个任务都是一个线程操作。

6.4 线程安全问题

在多线程的处理下,肯定会出现数据安全问题。如下:

@Test

public void test01(){

List list = new ArrayList<>();

for (int i = 0; i < 1000; i++) {

list.add(i);

}

System.out.println(list.size());

List listNew = new ArrayList<>();

// 使用并行流来向集合中添加数据

list.parallelStream()

//.forEach(s->listNew.add(s));

.forEach(listNew::add);

System.out.println(listNew.size());

}

运行效果:

839

或者直接抛异常

java.lang.ArrayIndexOutOfBoundsException

at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)

at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)

at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)

at java.lang.reflect.Constructor.newInstance(Constructor.java:423)

at java.util.concurrent.ForkJoinTask.getThrowableException(ForkJoinTask.java:598)

Caused by: java.lang.ArrayIndexOutOfBoundsException: 366

at java.util.ArrayList.add(ArrayList.java:463)

针对这个问题,我们的解决方案有哪些呢?

  1. 加同步锁

  2. 使用线程安全的容器

  3. 通过Stream中的toArray/collect操作

实现:

/**

  • 加同步锁

*/

@Test

public void test02(){

List listNew = new ArrayList<>();

Object obj = new Object();

IntStream.rangeClosed(1,1000)

.parallel()

.forEach(i->{

synchronized (obj){

listNew.add(i);

}

});

System.out.println(listNew.size());

}

/**

  • 使用线程安全的容器

*/

@Test

public void test03(){

Vector v = new Vector();

Object obj = new Object();

IntStream.rangeClosed(1,1000)

.parallel()

.forEach(i->{

synchronized (obj){

v.add(i);

}

});

System.out.println(v.size());

}

/**

  • 将线程不安全的容器转换为线程安全的容器

*/

@Test

public void test04(){

List listNew = new ArrayList<>();

// 将线程不安全的容器包装为线程安全的容器

List synchronizedList = Collections.synchronizedList(listNew);

Object obj = new Object();

IntStream.rangeClosed(1,1000)

.parallel()

.forEach(i->{

synchronizedList.add(i);

});

System.out.println(synchronizedList.size());

}

/**

  • 我们还可以通过Stream中的 toArray方法或者 collect方法来操作

  • 就是满足线程安全的要求

*/

@Test

public void test05(){

List listNew = new ArrayList<>();

Object obj = new Object();

List list = IntStream.rangeClosed(1, 1000)

.parallel()

.boxed()

.collect(Collectors.toList());

System.out.println(list.size());

}

7.Fork/Join框架


parallelStream使用的是Fork/Join框架。Fork/Join框架自JDK 7引入。Fork/Join框架可以将一个大任务拆分为很多小任务来异步执行。 Fork/Join框架主要包含三个模块:

  1. 线程池:ForkJoinPool

  2. 任务对象:ForkJoinTask

  3. 执行任务的线程:ForkJoinWorkerThread

在这里插入图片描述

7.1 Fork/Join原理-分治法

ForkJoinPool主要用来使用分治法(Divide-and-Conquer Algorithm)来解决问题。典型的应用比如快速排序算法,ForkJoinPool需要使用相对少的线程来处理大量的任务。比如要对1000万个数据进行排序,那么会将这个任务分割成两个500万的排序任务和一个针对这两组500万数据的合并任务。以此类推,对于500万的数据也会做出同样的分割处理,到最后会设置一个阈值来规定当数据规模到多少时,停止这样的分割处理。比如,当元素的数量小于10时,会停止分割,转而使用插入排序对它们进行排序。那么到最后,所有的任务加起来会有大概2000000+个。问题的关键在于,对于一个任务而言,只有当它所有的子任务完成之后,它才能够被执行。

在这里插入图片描述

7.2 Fork/Join原理-工作窃取算法

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数Java工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注Java获取)

img

总结

阿里伤透我心,疯狂复习刷题,终于喜提offer 哈哈~好啦,不闲扯了

image

1、JAVA面试核心知识整理(PDF):包含JVMJAVA集合JAVA多线程并发,JAVA基础,Spring原理微服务,Netty与RPC,网络,日志,ZookeeperKafkaRabbitMQ,Hbase,MongoDB,Cassandra,设计模式负载均衡数据库一致性哈希JAVA算法数据结构,加密算法,分布式缓存,Hadoop,Spark,Storm,YARN,机器学习,云计算共30个章节。

image

2、Redis学习笔记及学习思维脑图

image

3、数据面试必备20题+数据库性能优化的21个最佳实践

image
《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!
G9nLmNzZG4ubmV0L3FxXzM4NTI2NTcz,size_16,color_FFFFFF,t_70)

7.2 Fork/Join原理-工作窃取算法

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数Java工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。[外链图片转存中…(img-x74UbP9L-1712928646116)]

[外链图片转存中…(img-cDFLs3G9-1712928646117)]

[外链图片转存中…(img-UGxnBXik-1712928646117)]

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注Java获取)

img

总结

阿里伤透我心,疯狂复习刷题,终于喜提offer 哈哈~好啦,不闲扯了

[外链图片转存中…(img-SGstSsC2-1712928646117)]

1、JAVA面试核心知识整理(PDF):包含JVMJAVA集合JAVA多线程并发,JAVA基础,Spring原理微服务,Netty与RPC,网络,日志,ZookeeperKafkaRabbitMQ,Hbase,MongoDB,Cassandra,设计模式负载均衡数据库一致性哈希JAVA算法数据结构,加密算法,分布式缓存,Hadoop,Spark,Storm,YARN,机器学习,云计算共30个章节。

[外链图片转存中…(img-w7XmbCTt-1712928646118)]

2、Redis学习笔记及学习思维脑图

[外链图片转存中…(img-MoMxZW2b-1712928646118)]

3、数据面试必备20题+数据库性能优化的21个最佳实践

[外链图片转存中…(img-4duBRbeS-1712928646118)]
《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值