JDK新特性---stream流

目录

一 ,stream流

1.1.特性

二.使用步骤

 1.创建Stream

2. 流的生成方法

3.获取Stream流的两种方式

三、 流的Intermediate方法(中间操作)

四、 流的Terminal方法(终结操作)

(1)约简操作

(2)收集操作

(3)查找与收集操作

五.常规操作

0.collect(toList())

 1.收集 Stream 流中的数据到集合中

 2.收集 Stream 流中的数据到数组中

3.Stream流中数据聚合/分组/分区/拼接操作

1.forEach

2.count

3.filter

4.limit

5.skip

6.map

7.flatMap

8.sorted

9.distinct

10.match

11.max / min

12.reduce

13.map 和 reduce 方法组合使用

14.mapToInt / mapToDouble / mapToLong

15.concat

16.allMatch / anyMatch / noneMatch 匹配相关

六.并行stream的使用

6.1 并行 Stream 流的获取方式(两种)

6.2.判断 Stream 流是串行流还是并行流

6.3 串行流、并行流、for循环 求和效率对比

6.4 并行 Stream 流 线程不安全问题

6.5 并行 Stream 流底层原理分析(Fork/Join 框架)

6.6 并行 Stream 流总结



一 ,stream流

1.1.特性

1.Stream流不是一种数据结构,不保存数据,它只是在原数据集上定义了一组操作。

 2.这些操作是惰性的,即每当访问到流中的一个元素,才会在此元素上执行这一系列操作。

 3.Stream不保存数据,故每个Stream流只能使用一次。

    所以这边有两个概念:流、管道。元素流在管道中经过中间操作的处理,最后由最终操作得到前面处理的结果。这里有2个操作:中间操作、最终操作。

   中间操作:返回结果都是Stream,故可以多个中间操作叠加。

   终止操作:用于返回我们最终需要的数据,只能有一个终止操作。

   使用Stream流,可以清楚地知道我们要对一个数据集做何种操作,可读性强。而且可以很轻松地获取并行化Stream流,不用自己编写多线程代码,可以更加专注于业务逻辑。默认情况下,从有序集合、生成器、迭代器产生的流或者通过调用Stream.sorted产生的流都是有序流,有序流在并行处理时会在处理完成之后恢复原顺序。无限流的存在,侧面说明了流是惰性的,即每当用到一个元素时,才会在这个元素上执行这一系列操作。

二.使用步骤

 1.创建Stream

2.转换Stream,每次转换原有Stream对象不改变,返回一个新的Stream对象(可以有多次转换)

    3.对Stream进行聚合操作,获取想要的结果

2. 流的生成方法

 1.Collection接口的stream()或parallelStream()方法

   2.静态的Stream.of()、Stream.empty()方法

   3.Arrays.stream(array, from, to)

   4.静态的Stream.generate()方法生成无限流,接受一个不包含引元的函数

   5.静态的Stream.iterate()方法生成无限流,接受一个种子值以及一个迭代函数

   6.Pattern接口的splitAsStream(input)方法

   7.静态的Files.lines(path)、Files.lines(path, charSet)方法

   8.静态的Stream.concat()方法将两个流连接起来

   

3.获取Stream流的两种方式

1.所有的 Collection 集合都可以通过 .stream() 方法来获取流;

2.使用 Stream 接口的 .of() 静态方法,可以获取流。

/**
 * TODO 获取 Stream 流的两种方式
 *
 * @author liuzebiao
 * @Date 2020-1-7 17:09
 */
public class getStreamDemo {
 
    public static void main(String[] args) {
 
        //方式1:根据Collection获取流
        //Collection接口中有一个默认的方法:default Stream<E> stream()
        //1.List获取流
        List<String> list = new ArrayList<>();
        Stream<String> stream01 = list.stream();
 
        //2.Set获取流
        Set<String> set = new HashSet<>();
        Stream<String> stream02 = set.stream();
 
        //3.Map获取流
        //Map 并没有继承自 Collection 接口,所有无法通过该 map.stream()获取流。但是可用通过如下三种方式获取:
        Map<String,String> map = new HashMap<>();
        Stream<String> stream03 = map.keySet().stream();
        Stream<String> stream04 = map.values().stream();
        Stream<Map.Entry<String, String>> stream05 = map.entrySet().stream();
 
        //方式2:Stream中的静态方法of获取流
        // static<T> Stream<T> of(T... values)  
        // T... values:可变参数,实际原理就是可变数组(传递String数组进去)
        
        //1.字符串获取流
        Stream<String> stream06 = Stream.of("aa", "bb", "cc");
 
        //2.数组类型(基本类型除外)
        String[] strs = {"aa","bb","cc"};
        Stream<String> stream07 = Stream.of(strs);
 
        //3.基本数据类型的数组
        int[] arr = {1,2,3,4};
        //看着没报错,但是看到返回值是 int[],这是 Stream流把整个数组看做一个元素来操作,而不是操作数组中的int元素(这样子是不行的!!!)
        Stream<int[]> stream08 = Stream.of(arr);
    }
}

三、 流的Intermediate方法(中间操作)

 1.filter(Predicate) :将结果为false的元素过滤掉

   2.map(fun) :转换元素的值,可以用方法引元或者lambda表达式

   3.flatMap(fun) :若元素是流,将流摊平为正常元素,再进行元素转换

   4.limit(n) :保留前n个元素

   5.skip(n) :跳过前n个元素

   6.distinct() :剔除重复元素

   7.sorted() :将Comparable元素的流排序

   8.sorted(Comparator) :将流元素按Comparator排序

   9.peek(fun) :流不变,但会把每个元素传入fun执行,可以用作调试

四、 流的Terminal方法(终结操作)

(1)约简操作

   1.reduce(fun) :从流中计算某个值,接受一个二元函数作为累积器,从前两个元素开始持续应用它,累积器的中间结果作为第一个参数,流元素作为第二个参数

   2.reduce(a, fun) :a为幺元值,作为累积器的起点

   3.reduce(a, fun1, fun2) :与二元变形类似,并发操作中,当累积器的第一个参数与第二个参数都为流元素类型时,可以对各个中间结果也应用累积器进行合并,但是当累积器的第一个参数不是流元素类型而是类型T的时候,各个中间结果也为类型T,需要fun2来将各个中间结果进行合并

(2)收集操作

   1.iterator():

   2.forEach(fun):

   3.forEachOrdered(fun) :可以应用在并行流上以保持元素顺序

   4.toArray():

   5.toArray(T[] :: new) :返回正确的元素类型

   6.collect(Collector):

   7.collect(fun1, fun2, fun3) :fun1转换流元素;fun2为累积器,将fun1的转换结果累积起来;fun3为组合器,将并行处理过程中累积器的各个结果组合起来

(3)查找与收集操作

   1.max(Comparator):返回流中最大值

   2.min(Comparator):返回流中最小值

   3.count():返回流中元素个数

   4.findFirst() :返回第一个元素

   5.findAny() :返回任意元素

   6.anyMatch(Predicate) :任意元素匹配时返回true

   7.allMatch(Predicate) :所有元素匹配时返回true

   8.noneMatch(Predicate) :没有元素匹配时返回true

五.常规操作

0.collect(toList())

由Stream 里的值生成一个列表,是一个及早求值操作

很多Stream 操作都是惰性求值,因此调用Stream 一系列方法之后

还需最后再调用一个类似collect 的及早求值方法返回集合

开篇的例子:(再将符合要求的字符串放到一个新的集合里)

使用filter过滤器:遍历数据并检查其中的元素

package org.xxxx.demo01;
 
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
 
public class Demo01 {
	public static void main(String[] args) {
		// 创建集合
		List<String> list = new ArrayList<>();
 
		// 添加元素
		list.add("sdf");
		list.add("a");
		list.add("asdf");
		list.add("d");
		list.add("basdfgh");
 
		// 统计长度大于2的
		long count = list.stream().filter((s) -> s.length() > 2).count();
 
		// 将符合要求的放入集合
		List<String> list2 = list.stream().filter((s) -> s.length() > 2).collect(Collectors.toList());
 
		System.out.println(count);
		list2.forEach(System.out :: println);
	}
 
}

 1.收集 Stream 流中的数据到集合中

//1.收集数据到list集合中
stream.collect(Collectors.toList())
//2.收集数据到set集合中
stream.collect(Collectors.toSet())
//3.收集数据到指定的集合中
Collectors.toCollection(Supplier<C> collectionFactory)
stream.collect(Collectors.joining())
    
    
    /**
 * 收集Stream流中的数据到集合中
 * 备注:切记Stream流只能被消费一次,流就失效了
 * 如下只是示例代码
 */
public class CollectDataToCollection{
 
    public static void main(String[] args) {
        //Stream 流
        Stream<String> stream = Stream.of("aaa", "bbb", "ccc", "bbb");
        //收集流中的数据到集合中
        //1.收集流中的数据到 list
        List<String> list = stream.collect(Collectors.toList());
        System.out.println(list);
 
        //2.收集流中的数据到 set
        Set<String> collect = stream.collect(Collectors.toSet());
        System.out.println(collect);
 
        //3.收集流中的数据(ArrayList)(不收集到list,set等集合中,而是)收集到指定的集合中
        ArrayList<String> arrayList = stream.collect(Collectors.toCollection(ArrayList::new));
        System.out.println(arrayList);
 
        //4.收集流中的数据到 HashSet
        HashSet<String> hashSet = stream.collect(Collectors.toCollection(HashSet::new));
        System.out.println(hashSet);
    }
}

 2.收集 Stream 流中的数据到数组中

//1.使用无参,收集到数组,返回值为 Object[](Object类型将不好操作)
Object[] toArray();
//2.使用有参,可以指定将数据收集到指定类型数组,方便后续对数组的操作
<A> A[] toArray(IntFunction<A[]> generator);


/**
 * 收集Stream流中的数据到数组中
 * 备注:切记Stream流只能被消费一次,流就失效了
 * 如下只是示例代码
 */
public class CollectDataToArray{
 
    public static void main(String[] args) {
        //Stream 流
        Stream<String> stream = Stream.of("aaa", "bbb", "ccc", "bbb");
         
        //2.1 使用 toArray()无参
        Object[] objects = stream.toArray();
        for (Object o: objects) {//此处无法使用.length() 等方法
            System.out.println("data:"+o);
        }
 
        //2.2 使用有参返回指定类型数组
        //无参不好的一点就是返回的是 Object[] 类型,操作比较麻烦.想要拿到长度,Object是拿不到长度的
        String[] strings = stream.toArray(String[]::new);
        for(String str : strings){
            System.out.println("data:"+str + ",length:"+str.length());
        }
    }
}

3.Stream流中数据聚合/分组/分区/拼接操作

/**
 * TODO Student实体类
 *
 * @author liuzebiao
 * @Date 2020-1-10 13:38
 */
public class Student {
 
    private String name;
 
    private int age;
 
    private int score;
 
    public Student(String name, int age, int score) {
        this.name = name;
        this.age = age;
        this.score = score;
    }
 
    public String getName() {
        return name;
    }
 
    public void setName(String name) {
        this.name = name;
    }
 
    public int getAge() {
        return age;
    }
 
    public void setAge(int age) {
        this.age = age;
    }
 
    public int getScore() {
        return score;
    }
 
    public void setScore(int score) {
        this.score = score;
    }
 
 
    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", score=" + score +
                '}';
    }
}

1.聚合操作
//最大值
Collectors.maxBy();
//最小值
Collectors.minBy();
//总和
Collectors.summingInt();/Collectors.summingDouble();/Collectors.summingLong();
//平均值
Collectors.averagingInt();/Collectors.averagingDouble();/Collectors.averagingLong();
//总个数
Collectors.counting();

/**
 * Stream流数据--聚合操作
 * 备注:切记Stream流只能被消费一次,流就失效了
 * 如下只是示例代码
 * @author liuzebiao
 * @Date 2020-1-10 13:37
 */
public class CollectDataToArray{
 
    public static void main(String[] args) {
        Stream<Student> studentStream = Stream.of(
                new Student("赵丽颖", 58, 95),
                new Student("杨颖", 56, 88),
                new Student("迪丽热巴", 56, 99),
                new Student("柳岩", 52, 77)
        );
 
        //聚合操作
        //获取最大值(Stream流 max()方法亦可)
        //max()方法实现
        //Optional<Student> max = studentStream.max((s1, s2) -> s1.getScore() - s2.getScore());
        //(聚合)实现
        Optional<Student> max = studentStream.collect(Collectors.maxBy((s1, s2) -> s1.getScore() - s2.getScore()));
        System.out.println("最大值:"+max.get());
 
        //获取最小值(Stream流 min()方法亦可)
        //min()方法实现
        //Optional<Student> min = studentStream.max((s1, s2) -> s2.getScore() - s1.getScore());
        //(聚合)实现
        Optional<Student> min = studentStream.collect(Collectors.minBy((s1, s2) -> s1.getScore() - s2.getScore()));
        System.out.println("最小值:"+min.get());
 
        //求总和(使用Stream流的map()和reduce()方法亦可求和)
        //map()和reduce()方法实现
        //Integer reduce = studentStream.map(s -> s.getAge()).reduce(0, Integer::sum);
        //(聚合)简化前
        //Integer ageSum = studentStream.collect(Collectors.summingInt(s->s.getAge()));
        //(聚合)使用方法引用简化
        Integer ageSum = studentStream.collect(Collectors.summingInt(Student::getAge));
        System.out.println("年龄总和:"+ageSum);
 
        //求平均值
        //(聚合)简化前
        //Double avgScore = studentStream.collect(Collectors.averagingInt(s->s.getScore()));
        //(聚合)使用方法引用简化
        Double avgScore = studentStream.collect(Collectors.averagingInt(Student::getScore));
        System.out.println("分数平均值:"+avgScore);
 
        //统计数量(Stream流 count()方法亦可)
        //count()方法实现
        //long count = studentStream.count();
        //(聚合)统计数量
        Long count = studentStream.collect(Collectors.counting());
        System.out.println("数量为:"+count);
    }
}

最大值:Student{name='迪丽热巴', age=56, score=99}
最小值:Student{name='柳岩', age=52, score=77}
年龄总和:222
分数平均值:89.75
数量为:4
    
 
 2.分组操作
 /**
 * Stream流数据--分组操作
 * 备注:切记Stream流只能被消费一次,流就失效了
 * 如下只是示例代码
 * @author liuzebiao
 * @Date 2020-1-10 13:37
 */
public class CollectDataToArray{
 
    public static void main(String[] args) {
        Stream<Student> studentStream = Stream.of(
                new Student("赵丽颖", 52, 56),
                new Student("杨颖", 56, 88),
                new Student("迪丽热巴", 56, 99),
                new Student("柳岩", 52, 53)
        );
        
        //1.按照具体年龄分组
        Map<Integer, List<Student>> map = studentStream.collect(Collectors.groupingBy((s -> s.getAge())));
        map.forEach((key,value)->{
            System.out.println(key + "---->"+value);
        });
 
        //2.按照分数>=60 分为"及格"一组  <60 分为"不及格"一组
        Map<String, List<Student>> map = studentStream.collect(Collectors.groupingBy(s -> {
            if (s.getScore() >= 60) {
                return "及格";
            } else {
                return "不及格";
            }
        }));
        map.forEach((key,value)->{
            System.out.println(key + "---->"+value);
        });
    }
}
    
3.多级分组操作
/**
 * Stream流数据--多级分组操作
 * 备注:切记Stream流只能被消费一次,流就失效了
 * 如下只是示例代码
 * @author liuzebiao
 * @Date 2020-1-10 13:37
 */
public class CollectDataToArray{
 
    public static void main(String[] args) {
        Stream<Student> studentStream = Stream.of(
                new Student("赵丽颖", 52, 95),
                new Student("杨颖", 56, 88),
                new Student("迪丽热巴", 56, 55),
                new Student("柳岩", 52, 33)
        );
 
        //多级分组
        //1.先根据年龄分组,然后再根据成绩分组
        //分析:第一个Collectors.groupingBy() 使用的是(年龄+成绩)两个维度分组,所以使用两个参数 groupingBy()方法
        //    第二个Collectors.groupingBy() 就是用成绩分组,使用一个参数 groupingBy() 方法
        Map<Integer, Map<Integer, Map<String, List<Student>>>> map = studentStream.collect(Collectors.groupingBy(str -> str.getAge(), Collectors.groupingBy(str -> str.getScore(), Collectors.groupingBy((student) -> {
            if (student.getScore() >= 60) {
                return "及格";
            } else {
                return "不及格";
            }
        }))));
 
        map.forEach((key,value)->{
            System.out.println("分数:" + key);
            value.forEach((k2,v2)->{
                System.out.println("\t" + v2);
            });
        });
    }
}

4.分区操作
//1.一个参数
partitioningBy(Predicate<? super T> predicate)
 
//2.两个参数(多级分区)
partitioningBy(Predicate<? super T> predicate, Collector<? super T, A, D> downstream)
    
   /**
 * Stream流数据--多级分组操作
 * 备注:切记Stream流只能被消费一次,流就失效了
 * 如下只是示例代码
 * @author liuzebiao
 * @Date 2020-1-10 13:37
 */
public class CollectDataToArray{
 
    public static void main(String[] args) {
        Stream<Student> studentStream = Stream.of(
                new Student("赵丽颖", 52, 95),
                new Student("杨颖", 56, 88),
                new Student("迪丽热巴", 56, 55),
                new Student("柳岩", 52, 33)
        );
 
        //分区操作
        Map<Boolean, List<Student>> partitionMap = studentStream.collect(Collectors.partitioningBy(s -> s.getScore() > 60));
        partitionMap.forEach((key,value)->{
            System.out.println(key + "---->" + value);
        });
    }
}

false---->[Student{name='迪丽热巴', age=56, score=55}, Student{name='柳岩', age=52, score=33}]
true---->[Student{name='赵丽颖', age=52, score=95}, Student{name='杨颖', age=56, score=88}]

5.拼接操作
//无参数--等价于 joining("");
joining()
//一个参数
joining(CharSequence delimiter)
//三个参数(前缀+后缀)
joining(CharSequence delimiter, CharSequence prefix,CharSequence suffix)
    
/**
 * Stream流数据--多级分组操作
 * 备注:切记Stream流只能被消费一次,流就失效了
 * 如下只是示例代码
 * @author liuzebiao
 * @Date 2020-1-10 13:37
 */
public class CollectDataToArray{
 
    public static void main(String[] args) {
        Stream<Student> studentStream = Stream.of(
                new Student("赵丽颖", 52, 95),
                new Student("杨颖", 56, 88),
                new Student("迪丽热巴", 56, 55),
                new Student("柳岩", 52, 33)
        );
        
        //拼接操作
        //无参:join()
        String joinStr1 = studentStream.map(s -> s.getName()).collect(Collectors.joining());
        System.out.println(joinStr1);
        //一个参数:joining(CharSequence delimiter)
        String joinStr2 = studentStream.map(s -> s.getName()).collect(Collectors.joining(","));
        System.out.println(joinStr2);
        //三个参数:joining(CharSequence delimiter, CharSequence prefix,CharSequence suffix)
        String joinStr3 = studentStream.map(s -> s.getName()).collect(Collectors.joining("—","^_^",">_<"));
        System.out.println(joinStr3);
    }
}
   赵丽颖杨颖迪丽热巴柳岩
赵丽颖,杨颖,迪丽热巴,柳岩
^_^赵丽颖—杨颖—迪丽热巴—柳岩>_< 

1.forEach

迭代集合中元素。接收一个 Lambda 表达式

然后在 Stream 的每一个元素上执行该表达式

此操作是及早求值方法

public static void main(String[] args){
        List<String> list = new ArrayList<>();
        Collections.addAll(list,"Mary","Lucy","James","Johson","Steve");
        //forEach()遍历
        //未简写
        //list.forEach((String str)->{
        //    System.out.println(str);
        //});
        //简写1
        //list.forEach(str-> System.out.println(str));
        //最终简写
        list.forEach(System.out::println);
    }

2.count

public class StreamDemo {
 
    public static void main(String[] args){
        List<String> list = new ArrayList<>();
        Collections.addAll(list,"Mary","Lucy","James","Johson","Steve");
        //count()计算集合中元素个数
        long count = list.stream().count();
        System.out.println("元素个数为:"+count);
    }
}

3.filter

public class StreamDemo {
 
    public static void main(String[] args){
        List<String> list = new ArrayList<>();
        Collections.addAll(list,"Mary","Lucy","James","Johson","Steve");
        //filter()过滤,返回以"J"开头的名字
        list.stream().filter(str->str.startsWith("J")).forEach(System.out::println);
 
        //使用BiPredicate,就搞复杂了,如下,没啥意思(只会让人看不懂,哈哈)
        //BiPredicate<String,String> consumer = String::startsWith;
        //list.stream().filter(str->consumer.test(str,"J")).forEach(System.out::println);
    }
}

4.limit


public class StreamDemo {
 
    public static void main(String[] args){
        List<String> list = new ArrayList<>();
        Collections.addAll(list,"Mary","Lucy","James","Johson","Steve");
        //limit()截取,截取list集合前三个元素
        list.stream().limit(3).forEach(System.out::println);
    }
}

5.skip

public class StreamDemo {
 
    public static void main(String[] args){
        List<String> list = new ArrayList<>();
        Collections.addAll(list,"Mary","Lucy","James","Johson","Steve");
        //skip()跳过list集合前2个元素,获取剩下的元素
        list.stream().skip(2).forEach(System.out::println);
    }
}

6.map

 map() 方法,可以将流中的元素映射到另一个流中。即:可以将一种类型的流转换为另一种类型的流(区别:map返回的是指定类型(比如int),而flatMap返回的还是一个Stream流),map() 方法是一个非终结方法。该接口需要一个 Function 函数式接口参数,可以将当前流中的T类型数据转换为另一种R类型的流。

       这个方法有三个对于原始类型的变种方法,分别是:mapToInt,mapToLong 和 mapToDouble。这三个方法也比较好理解,比如 mapToInt 就是把原始 Stream 转换成一个新的 Stream,这个新生成的 Stream 中的元素都是 int 类型。之所以会有这样三个变种方法,可以免除自动装箱/拆箱的额外消耗

public class StreamDemo {
 
    public static void main(String[] args){
        List<String> list = new ArrayList<>();
        Collections.addAll(list,"11","22","33","44","55");
        //通过map()方法,可以将String类型的流转换为int类型的流
        /*list.stream().map((String str)->{
              return Integer.parseInt(str);
          }).forEach((Integer num) -> {
              System.out.println(num);
          });*/
        //简化:
        //list.stream().map(str->Integer.parseInt(str)).forEach(str->System.out.println(str));
        //简化后:
        list.stream().map(Integer::parseInt).forEach(System.out::println);
    }
}

7.flatMap

   flatMap 的使用,同 map 类似。map只是一维 1对1 的映射,返回的是指定的类型;

      而flatMap返回的则还是一个Stream流,可以对其进行进一步操作。(区别:map返回的是指定类型(比如int),而flatMap返回的还是一个Stream流)

      我的理解为:假如你的集合流中包含子集合(或者需要更深进一步操作),那么使用 flatMap 可以返回该子集合的集合流
public class Province {
 
    private String name;
 
    private List<String> city;
 
    //get/set 方法
}
 
public class flatMapDemo{
    public static void main(String[] args) {
 
        List<Province> provinceList = new ArrayList<>();
 
        List<String> bjCityList = new ArrayList<>();
        bjCityList.add("海淀");
        bjCityList.add("朝阳");
 
        List<String> shCityList = new ArrayList<>();
        shCityList.add("黄埔");
        shCityList.add("闵行");
 
        Province bjProvince = new Province();
        bjProvince.setName("北京");
        bjProvince.setCity(bjCityList);
        provinceList.add(bjProvince);
 
        Province shProvince = new Province();
        shProvince.setName("上海");
        shProvince.setCity(shCityList);
        provinceList.add(shProvince);
 
        //使用map,需要多次forEach
        provinceList.stream().map(str->str.getCity()).forEach(cityList -> cityList.forEach(System.out::println));
 
        System.out.println("----------");
 
        //使用 flatMap,一次forEach即可
        provinceList.stream().flatMap(str->str.getCity().stream()).forEach(System.out::println);
    }
}

8.sorted

//根据元素的自然规律排序
Stream<T> sorted();
//根据比较器指定的规则排序
Stream<T> sorted(Comparator<? super T> comparator);
    因为 sorted() 方法是一个非终结方法,所以必须调用终止方法。

       场景:①sorted() 方法:按照自然规律,默认为升序排序。

                  ②sorted(Comparator comparator)  方法,按照指定的比较器规则排序(以降序为例)。 示例如下:

public class StreamDemo {
 
    public static void main(String[] args){
        //sorted():根据元素的自然规律排序
        Stream<Integer> stream01 = Stream.of(66,33,11,55);
        stream01 .sorted().forEach(System.out::println);
        
        //sorted(Comparator<? super T> comparator):根据比较器规则降序排序
        Stream<Integer> stream02 = Stream.of(66,33,11,55);
        stream02 .sorted((i1,i2)-> i2-i1).forEach(System.out::println);
    }
}

9.distinct

Stream<T> distinct();
  distinct() 方法,可以用来去除重复数据。因为 distinct() 方法是一个非终结方法,所以必须调用终止方法。

       去除重复数据,此处有几种情况:①基本类型去重   ②String类型去重    ③引用类型去重(对象去重)

       ①②使用 distinct() 方法可以直接去重  ③对象类型需要重写 equals() 和 hasCode() 方法,使用 distinct() 方法才能去重成功

public class StreamDemo {
 
    public static void main(String[] args){
        //基本类型去重
        Stream<Integer> stream01 = Stream.of(66,33,11,55,33,22,55,66);
        stream01 .distinct().forEach(System.out::println);
 
        //字符串去重
        Stream<String> stream02 = Stream.of("AA","BB","AA");
        stream02.distinct().forEach(System.out::println);
 
        //自定义对象去重
        //(Person对象类,有String name,Integer age 两个属性,两参数构造器,get/set()方法,重写了equals(),hashCode(),toString()方法。此处就不附Person实体类了)
        BiFunction<String,Integer,Person> fn1 = Person::new;
        Stream<Person> stream14 = Stream.of(fn1.apply("西施", 18), fn1.apply("貂蝉", 20), fn1.apply("王昭君", 22), fn1.apply("杨玉环", 23), fn1.apply("杨玉环", 23));
        stream14.distinct().forEach(System.out::println);
    }
}

10.match

//allMatch 全匹配(匹配所有,所有元素都需要满足条件-->返回true)
boolean allMatch(Predicate<? super T> predicate);
//anyMatch 匹配某个元素(只要有一个元素满足条件即可-->返回true)
boolean anyMatch(Predicate<? super T> predicate);
//noneMatch 匹配所有元素(所有元素都不满足指定条件-->返回true)
boolean noneMatch(Predicate<? super T> predicate);

public class StreamDemo {
 
    public static void main(String[] args){
        Stream<Integer> stream01 = Stream.of(5, 3, 6, 1);
        boolean allMatch = stream01.allMatch(i -> i > 0);
        System.out.println("allMatch匹配:"+allMatch);
 
        Stream<Integer> stream02 = Stream.of(5, 3, 6, 1);
        boolean anyMatch = stream02 .anyMatch(i -> i > 5);
        System.out.println("anyMatch匹配:"+anyMatch);
 
        Stream<Integer> stream03 = Stream.of(5, 3, 6, 1);
        boolean noneMatch = stream03 .noneMatch(i -> i < 3);
        System.out.println("noneMatch匹配:"+noneMatch);
    }
}


allMatch匹配:true
anyMatch匹配:true
noneMatch匹配:false

11.max / min

  max() 和 min() 方法,用来获取 Stream 流中的最大值和最小值
     
  public class StreamDemo {
 
    public static void main(String[] args){
        //max()
        Stream<Integer> stream01 = Stream.of(33, 11, 22, 5);
        Optional<Integer> max = stream01.max((i1, i2) -> i1 - i2);
        System.out.println("最大值:"+max.get());
 
        //min()
        Stream<Integer> stream02 = Stream.of(33, 11, 22, 5);
        Optional<Integer> min = stream02.min((i1, i2) -> i1 - i2);
        System.out.println("最小值:"+min.get());
    }
}   

12.reduce

求和流程:
  第一次:将默认值赋值给x,取出集合第一个元素赋值给y
  第二步:将上一次返回的结果赋值给x,取出集合第二个元素赋值给y
  第三步:继续执行第二步(如下图所示)

 

public class StreamDemo {
 
    public static void main(String[] args){
        //reduce():求和操作
        Stream<Integer> stream01 = Stream.of(4,3,5,6);
        Integer sum = stream01.reduce(0,(x,y)-> x + y);
        System.out.println("求和:"+sum);
 
        //reduce():求最大值操作
        Stream<Integer> stream01 = Stream.of(4,3,5,6);
        Integer sum = stream01.reduce(0,(x,y)-> x + y);
        System.out.println("最大值为:"+sum);
    }
}

13.map 和 reduce 方法组合使用

public class StreamDemo {
 
    /**
     * map() 和 reduce() 方法组合使用
     */
    public static void main(String[] args){
        BiFunction<String,Integer,Person> fn2 = Person::new;
 
        //1.求出所有年龄的总和(年龄为int值,默认为0,此处可以使用待默认值的reduce()方法)
        Stream<Person> stream01 = Stream.of(fn2.apply("刘德华", 58), fn2.apply("张学友", 56), fn2.apply("郭富城", 54), fn2.apply("黎明", 52));
        //基本写法:
        //Integer total = stream01.map(p -> p.getAge()).reduce(0,(x, y) -> x + y);
        //(方法引用)简化后:
        Integer total = stream01.map(p -> p.getAge()).reduce(0,Integer::sum);
        System.out.println("年龄总和为:"+total);
 
        //2.找出最大年龄
        Stream<Person> stream02 = Stream.of(fn2.apply("刘德华", 58), fn2.apply("张学友", 56), fn2.apply("郭富城", 54), fn2.apply("黎明", 52));
        //基本写法:        
        //Integer maxAge = stream02.map(p -> p.getAge()).reduce(0, (x, y) -> x > y ? x : y);
        //(方法引用)简化后:
        Integer maxAge = stream02.map(p -> p.getAge()).reduce(0, Integer::max);
        System.out.println("最大年龄为:"+maxAge);
    }
}

14.mapToInt / mapToDouble / mapToLong

//mapToInt()
IntStream mapToInt(ToIntFunction<? super T> mapper);
//mapToLong()
LongStream mapToLong(ToLongFunction<? super T> mapper);
//mapToDouble()
DoubleStream mapToDouble(ToDoubleFunction<? super T> mapper);

//1.Integer是一个类,占用的内存肯定比 int 大
//2.Stream流在操作时,会存在自动装箱和拆箱操作
Stream<Integer> stream = Stream.of(2,3,5,6,7);
 
//把大于3的打印出来(num在Stream流中是Integer类型,在与3比较时,显然会存在自动拆装箱问题),效率会有影响
stream.filter(num -> num > 3).forEach(System.out::println);
所以在 JDK8 中,对 Stream 流还新增了一个 mapToInt()方法。
该方法可以将流中操作的 Integer 包装类,在 Stream 流中转换成直接来操作 int 类型,
效率明显会高一点。
IntStream mapToInt(ToIntFunction<? super T> mapper);
 
//此时 mapToInt() 接收一个 ToIntFunction 的函数式接口
 
//接下来我们来分析参数:ToIntFunction<? super T> mapper
 
@FunctionalInterface
public interface ToIntFunction<T> {
 
    /**
     * Applies this function to the given argument.
     *
     * @param value the function argument
     * @return the function result
     */
    int applyAsInt(T value);
}
 
//我们发现:ToIntFunction 是一个函数式接口,里面仅有一个抽象方法 applyAsInt(),
//applyAsInt() 方法:接收一个参数 ,返回一个int型。接下来我们便知道如何使用Lambda表达式来使用 mapToInt() 方法了

public class StreamDemo {
 
    public static void main(String[] args){
 
        //使用 mapToInt()方法
        IntStream intStream = Stream.of(1, 2, 3, 4, 5, 6).mapToInt((Integer num) -> {
            return num.intValue();
        });
 
        //(使用方法引用)简化后
        IntStream intStream1 = Stream.of(1, 2, 3, 4, 5, 6).mapToInt(Integer::intValue);
 
        intStream1.filter(n->n>3).forEach(System.out::println);
        /**
         * 使用mapToInt(),返回值是一个IntStream类型.我们看一下它们的继承结构图(如下所示):
         * IntStream 和 Stream<Integer> 类型进行比较。发现他们都继承自 BaseStream。所以它们区别不大
         * 只不过 IntStream 内部操作的是 int 基本类型的数据,省去自动拆装箱过程。从而可以节省内存开销
         */
    }
}

15.concat

public static <T> Stream<T> concat(Stream<? extends T> a, Stream<? extends T> b) {
    Objects.requireNonNull(a);
    Objects.requireNonNull(b);
 
    @SuppressWarnings("unchecked")
    Spliterator<T> split = new Streams.ConcatSpliterator.OfRef<>(
        (Spliterator<T>) a.spliterator(), (Spliterator<T>) b.spliterator());
    Stream<T> stream = StreamSupport.stream(split, a.isParallel() || b.isParallel());
    return stream.onClose(Streams.composedClose(a, b));
}

 concat() 方法,可以将两个Stream流合并成一个流进行返回。如果是三个流,则需要两两合并,不能一次性合并三个流。concat() 方法是 Stream 接口的静态方法,我们可以直接使用【类名.方法名】调用。
 
 public class StreamDemo {
 
    public static void main(String[] args){
        //concat()方法
        Stream<Integer> aStream = Stream.of(1, 2, 3);
        Stream<Integer> bStream = Stream.of(4, 5, 6);
 
        Stream<Integer> concatStream = Stream.concat(aStream, bStream);
        concatStream.forEach(System.out::println);
    }
}

 

16.allMatch / anyMatch / noneMatch 匹配相关

allMatch:是不是Stream中的所有元素都满足给定的匹配条件

anyMatch:Stream中是否存在任何一个元素满足匹配条件

noneMatch:是不是Stream中的所有元素都不满足给定的匹配条件

// Stream中元素,所有元素长度都>1
boolean flag = Stream.of("one", "two", "three", "four").allMatch(str -> str.length() > 1);
System.out.println(flag);     // 返回值:true
 
// Stream中元素,是不是存在任何一个元素长度>1
boolean flag1 = Stream.of("one", "two", "three", "four").anyMatch(str -> str.length() > 4);
System.out.println(flag1);     // 返回值:true
 
// Stream中元素,所有元素长度是不是都 <10
boolean flag2 = Stream.of("one", "two", "three", "four").noneMatch(str -> str.length() < 1);
System.out.println(flag2);     // 返回值:true

六.并行stream的使用

6.1 并行 Stream 流的获取方式(两种)

/**
 * 并行 Stream 流的获取方式
 */
public class ParallelStream {
 
    public static void main(String[] args) {
        //方式一:通过.parallelStream()方法,直接获取并行Stream流
        List<String> list = new ArrayList<>();
        Stream<String> stream = list.parallelStream();
 
        //方式二:通过.parallel()方法,将串行流转成并行流
        Stream<String> parallelStream = list.stream().parallel();
 
        //并行Stream流测试
        Stream<Integer> stream = Stream.of(4, 5, 6, 3, 21, 56).parallel();
        stream.filter(num->{
            System.out.println(Thread.currentThread().getName() + "--" + num);
            return num>3;
        }).count();
    }
}

ForkJoinPool.commonPool-worker-5--6
ForkJoinPool.commonPool-worker-3--4
main--3
ForkJoinPool.commonPool-worker-4--21
ForkJoinPool.commonPool-worker-1--5
ForkJoinPool.commonPool-worker-2--56

6.2.判断 Stream 流是串行流还是并行流

//我们可以通过isParallel()方法来判断
public final boolean isParallel() {
    return sourceStage.parallel;
}

/**
 * 判断当前流是串行流、并行流
 */
public class isParallelDemo{
    public static void main(String[] args) {
        Stream<Integer> stream01 = Stream.of(4, 5, 6, 3, 21, 56);
        System.out.println(stream01); //false
        Stream<Integer> stream02 = Stream.of(4, 5, 6, 3, 21, 56).parallel();
        System.out.println(stream02); //true
    }
}

6.3 串行流、并行流、for循环 求和效率对比

/**
 * TODO for循环、串行Stream流、并行Stream流 对5亿个数字进行求和
 *
 * @author liuzebiao
 * @Date 2020-1-10 17:17
 */
public class EffectiveDemo {
 
    private int times = 500000000;
    long start;
    long end;
    @Before
    public void init(){
        start = System.currentTimeMillis();
    }
 
    @After
    public void destory(){
        end = System.currentTimeMillis();
        System.out.println("消耗时间:"+(end-start));
    }
 
    //for循环(求和5亿)
    @Test
    public void testFor(){
        int sum = 0;
        for (int i = 0; i < times; i++) {
            sum += i;
        }
        System.out.println(sum);
    }
 
    //串行Stream流(求和5亿)
    @Test
    public void testStream(){
        //5亿数字太大,所以此处使用 LongStream
        //range():需要传入开始节点和结束节点两个参数,返回的是一个有序的LongStream.包含开始节点和结束节点两个参数之间所有的参数,间隔为1.
        //rangeClosed():功能和range()类似.差别就是rangeClosed包含最后的结束节点,range()不包含。
        LongStream.rangeClosed(0, times).reduce(0, Long::sum);
    }
 
    //并行Stream流(求和5亿)
    @Test
    public void testParallelStream(){
        LongStream.rangeClosed(0, times).parallel().reduce(0, Long::sum);
    }
}

(for循环)消耗时间:130
(串行Stream流)消耗时间:649
(并行Stream流)消耗时间:85

6.4 并行 Stream 流 线程不安全问题

不安全范例:
public class ParallelUnSafeDemo{
    /**
     * 并行Stream流 线程不安全 案例
     */
    @Test
    public void parallelStreamNotice() throws InterruptedException {
        ArrayList<Integer> list = new ArrayList<>();
        IntStream.rangeClosed(1, 1000).parallel().forEach(i -> list.add(i));
        System.out.println("list="+list.size());
    }
}

list=865
    
解决方法:
针对如上情况,为什么会出现这种情况,我们先来分析一下。共总结出如下三种解决方案:

      1.①我们可以使用万能的 synchronized 同步代码块来解决线程安全性问题;②也可以使用 Collections 集合工具类,使用 synchronizedList() 方法,当我们传入一个线程不安全的 list 后,会给我们返回一个线程安全的 list ,然后我们便可以对线程安全的 list 进行操作,类似于 synchronized 同步代码块;

      2.我们分析发现 ArrayList 集合本身就是线程不安全的,所以我们可以使用线程安全的集合,比如:使用Vector来替换 ArrayList

      3.我们也可以调用 Stream 流的 collect()/toArray() 收集方法,它也会将集合变成线程安全的(此处会使用.boxed() 方法)

/**
 * 解决 parallelStream 线程安全问题
 */
public class StreamSafeResolve {
 
    @Test
    public void parallelStreamNotice() throws InterruptedException {
        
        //方案一:1.使用同步代码块
        Object obj = new Object();
        ArrayList<Integer> list = new ArrayList<>();
        IntStream.rangeClosed(1, 1000).parallel().forEach((str)->{
                synchronized (obj){
                    list.add(str);
                }
            }
        );
        System.out.println("list="+list.size());
 
        //2.使用 Collections 集合工具类,有一个 synchronizedList() 方法,传入一个不安全的list,会返回一个线程安全的list。然后对线程安全的list进行操作
        ArrayList<Integer> lists = new ArrayList<>();
        List<Integer> list = Collections.synchronizedList(lists);
        IntStream.rangeClosed(1, 1000).parallel().forEach((str)->{ list.add(str); });
        System.out.println("list="+list.size());
 
        //方案二:使用线程安全的集合类
        //Vector是线程安全的集合
        Vector<Integer> vector = new Vector<>();
        IntStream.rangeClosed(1, 1000).parallel().forEach((str)->{ vector.add(str); });
        System.out.println("vector="+vector.size());
 
       //方案三:调用 Stream 流的 collect()/toArray()收集方法。它也会变成线程安全的
        List<Integer> list = IntStream.rangeClosed(1, 1000).boxed().collect(Collectors.toList());
        System.out.println("list="+list.size());
    }
}

6.5 并行 Stream 流底层原理分析(Fork/Join 框架)

Fork/Join 框架主要包含三个模块:

  1. 线程池:ForkJoinPool
  2. 任务对象:ForkJoinTask
  1. 执行任务的线程:ForkJoinWorkerThread

 

 6.5.2 原理介绍

1.分治法

     ForkJoinPool 主要使用分治法(Divide-and-Conquer Algorithm)来解决问题。根据名称我们可以将其分为 Fork 和 Join 两个阶段。典型的应用比如快速排序算法。

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

 

2.工作窃取算法

Fork/Join 最核心的地方,就是利用了现代硬件设备多核。当一个操作完成的时候会有空闲的CPU,那么如何利用好这个空闲的CPU就成了提高性能的关键,而我们这里提到的工作窃取(work-stealing)算法就是整个 Fork/Join 框架的核心理念。Fork/Join 工作窃取算法,是指某个线程从其他队列里窃取任务来执行。

 

 

 

那么为什么需要使用工作窃取算法呢?假如我们需要做一个比较大的任务,我们可以把这个任务分割为若干互不依赖的子任务,为了减少线程间的竟争,于是把这些子任务分别放到不同的队列里,并为每个队列创建一个单独的线程来执行队列里的任务,线程和队列一一对应。比如 A 线程负责处理 A 队列里的任务。但是有的线程会先把自己队列里的任务干完,而其他线程对应的队列里还有任务等待处理。干完活的线程与其等着看,不如去帮其他线程干活,于是它就去其他线程的队列里窃取一个任务来执行。而在这时它们会访问同一个队列,所以为了减少窃取任务线程和被窃取任务线程之间的竞争,通常会使用双端队列,被窃取任务线程永远从双端队列的头部拿任务执行,而窃取任务的线程永远从双端队列的尾部拿任务执行。

工作窃取算法的优点是充分利用线程进行并行计算,并减少了线程间的竞争,其缺点是①在某些情况下还是存在竞争,比如双端队列里只有一个任务时。②消耗了更多的系统资源,比如创建多个线程和多个双端队列。

      上文中已经提到了Java8 引入了自动并行化的概念。它能够让一部分 Java 代码自动地以并行的方式执行,也就是我们使用了 ForkjoinPool 的 ParallelStream。

      对于 ForkJoinPool 通用线程池的线程数量,通常使用默认值就可以了,即运行时计算机的处理器数量。可以通过设置系统属性:java.util.concurrent.ForkJoinPool.common.parallelism = N(N 为线程数量),来调整 ForkJoinPool 的线程数量,可以尝试调整成不同的参数来观察每次的输出结果。

 6.5.3 Fork/Join 案例Demo

需求:使用 Fork/Join 计算 1-10000的和,当一个任务的计算数量大于3000时拆分任务,数量小于3000时计算。


1.普通求和
public class ForkJoinDemo {
    public static void main(String[] args) {
        //开始时间
        Long start = System.currentTimeMillis();
        long sum = 0l;
        for (long i = 1; i <= 9999999999L; i++) {
            sum+=i;
        }
        System.out.println(sum);
        //结束时间
        Long end = System.currentTimeMillis();
        System.out.println("消耗时间:"+(end-start));
    }
}

2.Fork/Join求和
/**
 * TODO Fork/Join 求和Demo
 * @author liuzebiao
 * @Date 2020-1-14 17:03
 */
public class ForkJoinDemo {

    public static void main(String[] args) {
        Long start = System.currentTimeMillis();
        //放入线程池
        ForkJoinPool pool = new ForkJoinPool();
        SumRecursiveTask task = new SumRecursiveTask(1, 59999999999L);
        Long result = pool.invoke(task);
        System.out.println("result="+result);
        Long end = System.currentTimeMillis();
        System.out.println("消耗时间:"+(end-start));
    }
}

//1.创建一个求和的任务
//RecursiveTask:表示一个任务
class SumRecursiveTask extends RecursiveTask<Long>{

    //大于3000要拆分(创建一个变量)
    //是否要拆分的临界值
    private static final long THRESHOLD = 3000L;

    //起始值
    private final long start;
    //结束值
    private final long end;

    //构造方法(传递起始值、结束值)
    public SumRecursiveTask(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;
            SumRecursiveTask left = new SumRecursiveTask(start,middle);
            left.fork();

            SumRecursiveTask right = new SumRecursiveTask(middle+1,end);
            right.fork();
            return left.join() +right.join();
        }
    }
}

6.6 并行 Stream 流总结

parallel 并行 Stream 流是线程不安全的;
parallel 并行 Stream 流使用的场景是 CPU 密集型的,只是做到别浪费 CPU,假如本身电脑的 CPU 的负载很大,那还到处用并行流,那并不能起到作用;
I/O 密集型、磁盘I/O、网络I/O 都属于 I/O 操作,这部分操作时较少消耗 CPU 资源,一般并行流中不适用于 I/O密集型的操作,就比如使用并行流进行大批量的消息推送,涉及到了大量 I/O,使用并行流反而慢了很多;
在使用并行流的时候,是无法保证元素的顺序的,也就是即使你使用了同步集合也只能保证元素都正确,但无法保证其中的顺序。
 

注:以上资料参考 来源:Java8 Stream:2万字20个实例,玩转集合的筛选、归约、分组、聚合_云深不知处-CSDN博客_java stream 聚合

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值