JDK1.8新特性——Stream


流定义:流是Java8引入的全新概念,它用来处理集合中的数据,暂且可以把它理解为一种高级集合。对集合的操作简单了。

生成流

  1. 集合创建串行流 stream()
List<String> strings = Arrays.asList("abc","","bc","efg","abcd","","jkl");
//过滤流里元素""并把过滤后的stream转成集合
List<String> collects = strings.stream().filter(string -> !string.isEmpty()).collect(Collectors.toList());

2.为数组生成流对象

String[] names={"a","b","c"};
Stream<String> stream = Arrays.stream(names);

3.直接将几个值变成流对象 Stream.of()

Stream<String> abc = Stream.of("abc", "hha", "hehh");

4.文件变成流

Stream<String> lines = Files.lines(Paths.get("文件路径"), Charset.defaultCharset());

5, 通过 Collection#stream 方法或 List#stream 方法或 Set#stream 方法用集合创建 Stream 对象:

 Stream<String> stream = collection.stream();
 Stream<String> stream = list.stream();
 Stream<String> stream = set.stream();

6,通过 Stream.generate(Supplier) 方法配合 Stream#limit 方法直接创建 Stream 对象
例如:

 Stream<Double> stream = Stream.generate(Math::random).limit(10);

逻辑上,因为通过 Stream.generate 方法生成的 Stream 对象中的数据的数量是无限的(即,你向 Stream 对象每次『要』一个对象时它都会每次生成一个返回给你,从而达到『无限个』的效果),所以,会结合 Stream#limit 方法来限定 stream 流中的数据总量。

7:通过 Stream.iterator(Final T, final UnaryOperator<T> f) 方法配合 Stream#limit 方法直接创建 Stream 对象,例如:

 Stream<Integer> stream = Stream.iterate(1, n -> n += 2);
 // 1, 3, 5, 7, 9, 11, 13, 15, 17, 19, ...

Stream.iterator 方法要求你提供 2 个参数:
数据序列中的第一个数。这个数字需要使用者人为指定。通常也被称为『种子』。
根据『前一个数字』计算『下一个数字』的计算规则。
整个序列的值就是:x, f(x), f(f(x)), f(f(f(x))), ...

逻辑上,因为通过 Stream.iterator 方法生成的 Stream 对象中的数据的数量是无限的
(即,你向 Stream 对象每次『要』一个对象时它都会每次生成一个返回给你,
从而达到『无限个』的效果),
所以,会结合 Stream#limit 方法来限定 stream 流中的数据总量。

遍历流 forEach()

jdk8集合是怎么遍历的:

 List<String> strings = Arrays.asList("abc","","bc","efg","abcd","","jkl");
 List<String> collects = strings.stream().filter(string -> !string.isEmpty()).collect(Collectors.toList());
 System.out.println("遍历集合:"); 
 //lamdba方式             
 collects.forEach(collect-> { System.out.println(collect+" ");});
 //方法引用方式
 collects.forEach(System.out::println);    

遍历流:
void forEach(Consumer<? super T> action);该方法接受一个Consumer函数式接口,可以传递lamdba表达式,进行消费数据

List<String> strings = Arrays.asList("abc","","bc","efg","abcd","","jkl");
System.out.println("遍历流:");                                                        
strings.stream().filter(string -> !string.isEmpty()).forEach(System.out::print);   

映射流map()

用于映射每个元素到对应的结果 。
<R> Stream<R> map(Function<? super T, ? extends R> mapper); Function可以将一种T类型转换成R类型,这种转换动作,称为映射。

 List<Integer> numbers=Arrays.asList(1,2,3,4);            
 List<Integer> collect = numbers.stream().map(number -> { 
     number++;                                            
     number = number * 2;                                 
     return number;                                       
 }).collect(Collectors.toList());                         
 collect.forEach(System.out::println);                    

过滤流 filter()

用于通过设置的条件过滤出元素, Stream<T> filter(Predicate<? super T> predicate);

 Stream.of("张三","李四","王五").filter(string ->string.startsWith("张")).forEach(System.out::print);

多条件:也可以使用多个filer

List<KfCollectPO> collect = kfCollectPOS.stream()
.filter(kfCollectPO -> resourceTypeSet.equals(kfCollectPO.getResourceType()) && kfCollectPO.getResourceId() == kfDataSetPO.getSetId())
.collect(Collectors.toList());

流的特点

Stream流属于管道流,只能被消费一次。第一个流调用完毕就会流转到下一个流上,而这时第一个流已经使用完毕,会自动关闭。

Stream<String>  stringStream= Stream.of("张三", "李四", "王五");
Stream<String> stringStream1 = stringStream.filter(string -> string.startsWith("张"));
stringStream1.forEach(System.out::print);
//第一个流已经关闭,在使用会报错:java.lang.IllegalStateException: stream has already been operated upon or closed
stringStream.forEach(System.out::print);

流统计count()

用于统计Stream中元素的个数

Stream<String>  stringStream= Stream.of("张三", "李四", "王五");
long count = stringStream.count();
System.out.println(count);

限制流limit()

用于获取指定数量的流

Stream<String>  stringStream= Stream.of("张三", "李四", "王五");
stringStream.limit(2).forEach(System.out::print);

skip(n) 跳过前n个元素

 Stream<String>  stringStream= Stream.of("张三", "李四", "王五");
 stringStream.skip(2).forEach(System.out::print);

limit()+skip()实现分页

int pageSize=3;
int pageNo=2;
Stream<String>  stringStream= Stream.of("张三", "李四", "王五","李六","逍遥子","杨过");
stringStream.skip(pageSize * (pageNo-1)).limit(pageSize).forEach(System.out::print);

组合流concat()

用于将两个流合并成一个流

Stream<String>  stringStream1= Stream.of("张三", "李四", "王五");
Stream<String>  stringStream2= Stream.of("李六","逍遥子","杨过");
Stream.concat(stringStream1,stringStream2 ).forEach(System.out::print);

流排序 sorted()

用于对流进行排序

List<Integer> str4=Arrays.asList(4,3,2,123);
str4.stream().sorted().forEach(System.out::println);
//倒叙
List<Integer> str4=Arrays.asList(4,3,2,123);
str4.stream().sorted(Comparator.reverseOrder()).forEach(System.out::println);

自定义排序规则:

//按照abcd排序
Stream.of("b","a","d","c","e","f").sorted(Comparator.comparing(String::hashCode)).forEach(System.out::print);
//倒序
Stream.of("b","a","d","c","e","f").sorted(Comparator.comparing(String::hashCode).reversed()).forEach(System.out::print);
//对集合排序,排序规则是元素的名字字段
list.stream().sorted(Comparator.comparing(Student::getAge));

Comparator

comparing方法入参为Function函数式接口,说明可以传一个lamdba表达式,可以使用方法引用优化,。
底层实际上使用的是compareTo方法进行比较,因为Function的出参继承自U,而U继承自Comparable接口,所以能使用compareTo方法

public static <T, U extends Comparable<? super U>> Comparator<T> comparing(
        Function<? super T, ? extends U> keyExtractor)
{
    Objects.requireNonNull(keyExtractor);
    return (Comparator<T> & Serializable)(c1, c2) -> keyExtractor.apply(c1).compareTo(keyExtractor.apply(c2));
}

去重distinct()

List<String> str3 = Arrays.asList("abc", "", "abc", "efg", "abcd","", "jkl");
List<String> collect = str3.stream().distinct().collect(Collectors.toList());
collect.forEach(System.out::println);

将多个stream 合并到一个Stream flatMap()

flatMap参数:Function<? super T, ? extends Stream<? extends R>

List<String> list = new ArrayList<>();
list.add("aaa bbb ccc");
list.add("ddd eee fff");
list.add("ggg hhh iii");
list.stream().map(s -> s.split(" ")).flatMap(string->{
    Stream<String> stream = Arrays.stream(string);
    return stream;
}).forEach(System.out::print);

分析:map把集合中的每一个元素按照" "分割并返回一个String数组,map完成后,流的类型变成Stream<String[]>flatMap先使用Array.stream将传进来的每个String[]转成Stream<String> 流,然后就将这些流连接成一个流并返回得到一个完整的Stream<String>

        List<String> list = new ArrayList<>();
        list.add("aaa bbb ccc");
        list.add("ddd eee fff");
        list.add("ggg hhh iii");
        
        //生成Stream<String[]>,里面包含三个String[]
        //[aaa, bbb, ccc]
        //[ddd, eee, fff]
        //[ggg, hhh, iii]
        Stream<String[]> stream1 = list.stream().map(s -> s.split(" "));
        //flatMap遍历生成Stream,每个元素都是一个String[]
        //里面的逻辑是,把数组转成Stream<String>
        //然后通过flatMap的遍历,把三个String[]生成的Stream<String>,都拼成一个Stream<String>
        stream1.flatMap(string->{
            //Arrays.stream可以为数组输出成流对象
            Stream<String> stream = Arrays.stream(string);
            return stream;
        }).forEach(System.out::print);

anyMatch() 是否匹配任一元素

流中是否至少有一个元素匹配给定的 T -> boolean 条件,有一个或多个元素满足条件,返回true,没有一个元素满足条件,返回false

String[] words=new String[]{"hello","world"};
boolean b = Arrays.stream(words).anyMatch(word -> {
   if ("hello".equals(word))
       return true;
   return false;
});
System.out.println(b);
true

allMatch() 是否匹配所有元素

流中是否所有元素都匹配给定的 T -> boolean 条件,全都匹配,返回true,否则,返回false

String[] words=new String[]{"hello","world"};
boolean b = Arrays.stream(words).allMatch(word -> {
    if ("hello".equals(word))
        return true;
    return false;
});
System.out.println(b);
false

String[] words=new String[]{"hello","hello"};
boolean b = Arrays.stream(words).allMatch(word -> {
    if ("hello".equals(word))
        return true;
    return false;
});
System.out.println(b);
true

noneMatch() 是否未匹配所有元素

流中是否没有元素匹配给定的 T -> boolean 条件,没有一个元素匹配,返回true,有一个元素匹配,返回fasle

String[] words=new String[]{"hello","world"};
boolean b = Arrays.stream(words).noneMatch(word ->"123".equals(word));
System.out.println(b);
true
String[] words=new String[]{"hello","world"};
boolean b = Arrays.stream(words).noneMatch(word ->"hello".equals(word));
System.out.println(b);
false

findAny()找到其中一个元素

找到其中一个元素 (使用 stream() 时找到的是第一个元素;使用 parallelStream() 并行时找到的是其中一个元素)
返回一个Optional类型的元素
Optional介绍

List<String> str3 = Arrays.asList("abc", "", "abc", "efg", "abcd","", "jkl");
Optional<String> optionalS = str3.stream().findAny();
System.out.println(optionalS.get());

Optional<String> any = str3.parallelStream().findAny();
System.out.println(any.get());
abc
abcd

findFirst() 获取第一个元素

找到第一个元素

List<String> str3 = Arrays.asList("abc", "", "abc", "efg", "abcd","", "jkl");
Optional<String> optionalS = str3.stream().findFirst();
System.out.println(optionalS.get());

Optional<String> any = str3.parallelStream().findFirst();
System.out.println(any.get());

abc
abc

归约 reduce()能实现归约

reduce函数接收两个参数:1.初始值 2.进行归约操作的Lambda表达式
自定义Lambda表达式实现规约操作,如果当前流的元素为数值类型,那么可以使用Integer提供的sum函数代替自定义的Lambda表达式,Integer类还提供了minmax等一系列数值操作,当流中元素为数值类型时可以直接使用。

List<Person> list=new ArrayList<>();
list.add(new Person("张三",24));
list.add(new Person("李四",26));
list.add(new Person("王五",28));
//求和 0表示初始值
int sum1 = list.stream().map(Person::getAge).reduce(0, (a, b) -> a + b);
int sum2 = list.stream().map(Person::getAge).reduce(0, Integer::sum);
System.out.println("和:"+sum1);
System.out.println("和:"+sum2);
//求积
Integer reduce = list.stream().map(Person::getAge).reduce(1, (a, b) -> a * b);
System.out.println("积:"+reduce);
//最大值
Optional<Integer> reduce1 = list.stream().map(Person::getAge).reduce(Integer::max);
System.out.println("最大值:"+reduce1.get());

reduce的第一个参数表示初试值
reduce的第二个参数为需要进行的归约操作,它接收一个拥有两个参数的Lambda表达式,reduce会把流中的元素两两输给Lambda表达式,最后将计算出来。
reduce()第一个参数是上次累计的和,第二个参数是数据流的下一个元素

数值流的使用

采用reduce进行数值操作会涉及到基本数值类型和引用数值类型之间的装箱、拆箱操作,因此效率较低当流操作为纯数值操作时,使用数值流能获得较高的效率。
StreamAPI提供了三种数值流:IntStream、DoubleStream、LongStream
将普通流转换成数值流的三种方法:mapToInt、mapToDouble、mapToLong
数值流转成普通流:boxed()
将串行数值流转成并行数值流:parallel()
每种数值流都提供了数值计算函数,如max、min、sum、average等。

OptionalDouble average = list.stream().map(Person::getAge).mapToInt(Integer::intValue).average();
System.out.println(average.getAsDouble());

max,min返回结果为OptionalInt,sumint;Double,Long类似

数值范围

IntStreamLongStream 拥有 rangerangeClosed 方法用于数值范围处理。

  • IntStreamrangeClosed(int, int) / range(int, int)
  • LongStreamrangeClosed(long, long) / range(long, long)

这两个方法的区别在于一个是闭区间,一个是半开半闭区间:

  • rangeClosed(1, 100)[1, 100]
  • range(1, 100)[1, 100)
    我们可以利用 IntStream.rangeClosed(1, 100) 生成 1 到 100 的数值流:
long sum = LongStream.rangeClosed(0L, 100).sum();

collect()收集器

collect 方法作为终端操作,接受的是一个 Collector 接口参数,能对数据进行一些收集归总操作

收集

最常用的方法,把流中所有元素收集到一个 List, SetCollection

  • toList
  • toSet
  • toCollection
  • toMap
    public static void main(String[] args) {
        List<Person> data = new ArrayList<>();
        Person p1=new Person("张三",1);
        Person p2=new Person("李四",2);
        Person p3=new Person("王五",3);
        data.add(p1);
        data.add(p2);
        data.add(p3);
		//toMap 第一个参数为key  第二个参数为value
        Map<String, Integer> collect = data.stream().collect(Collectors.toMap(Person::getName, Person::getAge));
//        Map<String, Integer> collect = data.stream().collect(Collectors.toMap(item->item.getName(), item->item.getAge()));
        System.out.println(collect);
//        {李四=2, 张三=1, 王五=3}
//        {李四=2, 张三=1, 王五=3}
//        {李四=2, 张三=1, 王五=3}
    }
    @AllArgsConstructor
    @Data
    static class Person{
        private String name;
        private int age;
    }

汇总

counting计数

用于计算数据的数量:

 List<Integer> numbers=Arrays.asList(1,2,3,4);
 Long collect = numbers.stream().collect(Collectors.counting());
 System.out.println(collect);
 4
 //也可以不使用收集器的计数器                             
 long count = numbers.stream().count();   

推荐第二种

summingInt ,summingLong ,summingDouble

summing,没错,也是计算总和,不过这里需要一个函数参数

List<Person> list=new ArrayList<>();
list.add(new Person("张三",24));
list.add(new Person("李四",26));
list.add(new Person("王五",28));

//使用收集器的规约操作
Integer collect = list.stream().collect(Collectors.summingInt(Person::getAge));
System.out.println(collect);
//使用reduce的规约操作
Integer reduce = list.stream().map(Person::getAge).reduce(0, Integer::sum);
System.out.println(reduce);
//使用数值流的规约操作
int sum = list.stream().mapToInt(Person::getAge).sum();
System.out.println(sum);

三种规约操作,函数式编程通常提供了多种方式来完成同一种操作,推荐使用数值流。

averagingInt,averagingLong,averagingDouble
List<Person> list=new ArrayList<>();
list.add(new Person("张三",24));
list.add(new Person("李四",26));
list.add(new Person("王五",28));

//使用收集器的规约操作
Double collect = list.stream().collect(Collectors.averagingInt(Person::getAge));
System.out.println(collect);
//使用数值流的规约操作
double asDouble = list.stream().mapToInt(Person::getAge).average().getAsDouble();
System.out.println(asDouble);
summarizingInt,summarizingLong,summarizingDouble

这三个方法比较特殊,比如 summarizingInt 会返回 IntSummaryStatistics 类型

IntSummaryStatistics collect = list.stream().collect(Collectors.summarizingInt(Person::getAge));
System.out.println("最大值:"+collect.getMax());
System.out.println("最小值:"+collect.getMin());
System.out.println("和:"+collect.getSum());
System.out.println("平均值:"+collect.getAverage());
System.out.println("总数:"+collect.getCount());

IntSummaryStatistics 包含了计算出来的平均值,总数,总和,最值。

取最值

maxBy,minBy 两个方法,需要一个 Comparator 接口作为参数

Optional<Person> collect = list.stream().collect(Collectors.maxBy(Comparator.comparing(Person::getAge)));
System.out.println(collect.get().getAge());
//也可以直接使用 max 方法获得同样的结果
Optional<Person> max = list.stream().max(Comparator.comparing(Person::getAge));
System.out.println(max.get().getAge());
joining 连接字符串

对流里面的字符串元素进行连接,其底层实现用的是专门用于字符串连接的 StringBuilder

String collect = list.stream().map(Person::getName).collect(Collectors.joining());
System.out.println(collect);
结果:张三李四王五
String collect = list.stream().map(Person::getName).collect(Collectors.joining(":"));
System.out.println(collect);
结果:张三:李四:王五
//第一个参数是连接符,第二个参数是前缀,第三个参数是后缀
String collect = list.stream().map(Person::getName).collect(Collectors.joining(":","开始:"," 结束"));
System.out.println(collect);
开始:张三:李四:王五 结束
归约操作

若你需要自定义一个归约操作,那么需要使用Collectors.reducing函数,该函数接收三个参数:

  • 第一个参数为归约的初始值
  • 第二个参数为归约操作进行的字段
  • 第三个参数为归约操作的过程

例子一:

List<Integer> numbers=Arrays.asList(1,2,3,4);
Integer collect = numbers.stream().collect(Collectors.reducing(0, Integer::intValue, (a, b) -> a + b));
System.out.println(collect);

例子二:

Optional<Integer> sumAge = list.stream()
                      .collect(Collectors.reducing(0,Person::getAge,(i,j)->i+j));

第三个参数表示归约的过程。这个参数接收一个Lambda表达式,而且这个Lambda表达式一定拥有两个参数,分别表示当前相邻的两个元素。由于我们需要累加,因此我们只需将相邻的两个元素加起来即可。

groupingBy 分组
https://blog.csdn.net/zhouzhiwengang/article/details/112319054

分组就是将流中的元素按照指定类别进行划分,类似于SQL语句中的GROUPBY

Map<Integer, List<Person>> collect = list.stream().collect(Collectors.groupingBy(Person::getAge));
collect.forEach((k,v)->{
   System.out.println(k);
   v.stream().forEach(a->{
       System.out.println(a.getName()+" "+a.getAge());
   });
});
一级分组
Map<String, List<Teacher>> collect = teachers.stream().collect(Collectors.groupingBy(teacher -> {
  if (teacher.getAge() > 60)
      return "老年老师";
  else if (teacher.getAge() > 40)
      return "中年老师";
  else
      return "青年老师";
}));
//遍历map
collect.forEach((k,v)-> {
  System.out.println(k);
  v.forEach(teacher -> System.out.println(teacher.getId()+" "+teacher.getName()+" "+teacher.getAge()));
});

groupingby函数接收一个Lambda表达式,该表达式返回String类型的字符串,groupingby会将当前流中的元素按照Lambda返回的字符串进行分组。
分组结果是一个Map< String,List< Person>>,Map的键就是组名,Map的值就是该组的Perosn集合。

多级分组

多级分组可以支持在完成一次分组后,分别对每个小组再进行分组。
使用具有两个参数的groupingby重载方法即可实现多级分组。

第一个参数:一级分组的条件
第二个参数:一个新的groupingby函数,该函数包含二级分组的条件

//多级分组  先通过年龄分组,再通过性别分组
Map<String, Map<String, List<Teacher>>> collect = teachers.stream()
.collect(groupingBy(teacher -> {
    if (teacher.getAge() > 60)
        return "老年老师";
    else if (teacher.getAge() > 40)
        return "中年老师";
    else
        return "青年老师";
}, groupingBy(Teacher::getSex)));
//遍历map
collect.forEach((k,v)-> {
    System.out.println(k);
    v.forEach((a,b) -> {
        System.out.println(a);
        b.forEach(teacher -> System.out.println(teacher.getName()+" "+teacher.getAge()+" "+teacher.getSex()));
    });
    System.out.println("-----------------------------------------------------------------");
});
按组收集数据
Map<Integer, Long> collect = list.stream()
						.collect(Collectors.groupingBy(Person::getAge, Collectors.counting()));
collect.forEach((k,v)->{
  System.out.println(k+" "+v);
});

此时会返回一个Map< Integer,Long>类型的map,该map的键为组名,map的值为该组的元素个数。

partitioningBy 分区

分区与分组的区别在于,分区是按照 truefalse 来分的,因此partitioningBy 接受的参数的 lambda 也是 T -> boolean

Map<Boolean, List<Person>> collect = list.stream()
			.collect(Collectors.partitioningBy(person -> person.getAge() <= 26));
System.out.println(collect.toString());
{false=[Person{name='王五', age=28}, Person{name='小李子', age=28}],
 true=[Person{name='张三', age=24}, Person{name='李四', age=26}]}

同样地 partitioningBy 也可以添加一个收集器作为第二参数,进行类似 groupBy 的多重分区等等操作。

并行程序parallelStream()是流并行处理程序的代替方法

并行流就是把内容分成多个数据块,使用不同的线程分别处理每个数据块的流。这也是流的一大特点,要知道,在 Java 7 之前,并行处理数据集合是非常麻烦的,你得自己去将数据分割开,自己去分配线程,必要时还要确保同步避免竞争。不是所有情况的适合,有些时候并行甚至比顺序进行效率更低,而有时候因为线程安全问题,还可能导致数据的处理错误。

普通的并行流
//lists.parallelStream()

long start = System.currentTimeMillis();
//串行数值流转成并行数值流    261  262 256
long sum = LongStream.rangeClosed(0L, 10_0000_0000L).parallel().reduce(0, Long::sum);
//只用数值流   642  702 676
//long sum = LongStream.rangeClosed(0L, 10_0000_0000L).reduce(0, Long::sum);
long end = System.currentTimeMillis();
System.out.println("sum="+sum+" 时间:"+(end-start));

并行流下的线程安全问题

复现问题:筛选出集合integers中取余2为0的元素并放到一个新集合中
结果:并行流出现线程安全问题:要么ArrayIndexOutOfBoundsException,要么strings.size()<500

List<Integer> integers = new ArrayList<>();
for (int i = 0; i < 1000; i++ ) {
	integers.add(i);
}
List<String> strings = new ArrayList<>();
integers.parallelStream().filter(integer -> integer%2==0).forEach(i -> strings.add(i.toString()));
int count=0;
for (String string : strings) {
	System.out.println(string);
	count++;
}
System.out.println("数量:"+count);

在这里插入图片描述
解决方式1:使用线程安全的Vector

List<String> strings = new Vector<>(1000);
integers.parallelStream().filter(integer -> integer%2==0).forEach(i -> strings.add(i.toString()));
int count=0;
for (String string : strings) {
   System.out.println(string);
   count++;
}
System.out.println("数量:"+count);

在这里插入图片描述
解决方式2:使用集合工具提供的Collections.synchronizedList(new ArrayList<>())

List<String> strings = Collections.synchronizedList(new ArrayList<>());
integers.parallelStream().filter(integer -> integer%2==0).forEach(i -> strings.add(i.toString()));
int count=0;
for (String string : strings) {
    System.out.println(string);
    count++;
}
System.out.println("数量:"+count);

在这里插入图片描述
解决方式3:使用并发包下的CopyOnWriteArrayList

List<String> strings = new CopyOnWriteArrayList<>();
integers.parallelStream().filter(integer -> integer%2==0).forEach(i -> strings.add(i.toString()));
int count=0;
for (String string : strings) {
    System.out.println(string);
    count++;
}
System.out.println("数量:"+count);

在这里插入图片描述
上面三种方式,底层都使用了锁,前两种使用的synchronizedCopyOnWriteArrayList使用的lock锁,用了锁性能肯定会有所下降。
并行流的正确的使用方式:

List<Integer> collect = integers
	.parallelStream()
	.filter(integer -> integer % 2 == 0)
	.collect(Collectors.toList());
int count=0;
for (Integer integer : collect) {
   System.out.println(integer);
   count++;
}
System.out.println("数量:"+count);

在这里插入图片描述
不但没有出现异常和数量不一致的问题,而且还排序,在采用并行流收集元素到集合中时,最好调用collect方法,一定不要采用Foreach方法或者map方法

实际中遇到并行流的线程安全问题

// 获取当前建筑的所有楼层
List<String> allFloors = getAllFloors(workbenchStatisticsAO);

// 获取所有楼层的设备
List<DeviceVO> devices = allFloors.stream().parallel().map(id -> {
    // 不能放外面,放外面就是共享资源,并行执行的时候,会存在线程安全问题,因为这个共享资源会被修改,所以存在线程安全问题,如果只是读,是不会存在线程安全问题的
    GetDevicesAO getDevicesAO = BeanUtil.copyProperties(workbenchStatisticsAO, GetDevicesAO.class);
    // 获取当前楼层的设备
    getDevicesAO.setFloorId(id);
    List<DeviceVO> deviceVOList = deviceService.chillerList(getDevicesAO);
    return deviceVOList;
}).flatMap(Collection::stream).collect(Collectors.toList());

一个疑问

main函数启动java应用,按道理只有两条线程,一条main线程,一条GC线程。能参与并行流的线程只有main线程,如果只有main线程一条线程,那怎么称的上是并行呢?

public static void main(String[] args) {
    List<Double> doubles = new ArrayList<>();
    for (double i = 0; i < 10; i++ ) {
        doubles.add(i);
    }
    //i=10000                  i=100000
    //串行:4999.5 time:75     49999.5 time:86
    //并行:4999.5 time:76     49999.5 time:96
    long start = System.currentTimeMillis();
    double collect = doubles
        .stream()
        .mapToDouble(Double::doubleValue)
        .parallel()//加上就是并行数值流,注释就是串行数值流
        .peek(a-> System.out.println(Thread.currentThread().getName()))//查看线程
        .average()
        .getAsDouble();
    long end = System.currentTimeMillis();
    System.out.println(collect+" time:"+(end-start));
}
结果:
ForkJoinPool.commonPool-worker-3
ForkJoinPool.commonPool-worker-2
ForkJoinPool.commonPool-worker-5
ForkJoinPool.commonPool-worker-4
ForkJoinPool.commonPool-worker-7
main
ForkJoinPool.commonPool-worker-6
ForkJoinPool.commonPool-worker-1
ForkJoinPool.commonPool-worker-1
ForkJoinPool.commonPool-worker-7
4.5 time:77

经过测试查看,发现并行流底层使用的是并发包下的ForkJoin

附录

https://www.jianshu.com/p/e429c517e9cb
https://www.jianshu.com/p/ac2bcf2f9d48
https://blog.csdn.net/qq_38974634/article/details/81347604

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值