【小家java】java8新特性之---Stream API 详解 (Map-reduce、Collectors收集器、并行流、groupby多字段分组)(上)

简介: 【小家java】java8新特性之---Stream API 详解 (Map-reduce、Collectors收集器、并行流、groupby多字段分组)(上)

我们为什么需要 Stream API


Stream 作为 Java 8 的一大亮点,它与 java.io 包里的 InputStream 和 OutputStream 是完全不同的概念。


集合讲的是数据,流讲的是计算


image.png


Java 8 中的 Stream 是对集合(Collection)对象功能的增强,它专注于对集合对象进行各种非常便利、高效的聚合操作(aggregate operation),或者大批量数据操作 (bulk data operation)。Stream API 借助于同样新出现的 Lambda 表达式,极大的提高编程效率和程序可读性。


同时它提供串行和并行两种模式进行汇聚操作,并发模式能够充分利用多核处理器的优势,使用 fork/join 并行方式来拆分任务和加速处理过程。通常编写并行代码很难而且容易出错, 但使用 Stream API 无需编写一行多线程的代码,就可以很方便地写出高性能的并发程序。所以说,Java 8 中首次出现的 java.util.stream 是一个函数式语言+多核时代综合影响的产物。


浅谈聚合操作(Stream API能协助解决)


在传统的 J2EE 应用中,Java 代码经常不得不依赖于关系型数据库的聚合操作来完成诸如:


   客户每月平均消费金额


   最昂贵的在售商品


   取十个数据样本作为首页推荐


但在当今这个数据大爆炸的时代,在数据来源多样化、数据海量化的今天,很多时候不得不脱离 RDBMS,或者以底层返回的数据为基础进行更上层的数据统计。


这个时候,如果没有Java8提供的Stream API,那简直就是噩梦。在 Java 8 使用 Stream,代码更加简洁易读;而且使用并发模式,程序执行速度更快。


对Stream进一步理解


简单说,对 Stream 的使用就是实现一个 filter-map-reduce 过程,产生一个最终结果,或者导致一个副作用(side effect)。


Stream 不是集合元素,它不是数据结构并不保存数据,它是有关算法和计算的,它更像一个高级版本的 Iterator。


对于 Stream,用户只要给出需要对其包含的元素执行什么操作,比如 “过滤掉长度大于 10 的字符串”、“获取每个字符串的首字母”等,Stream 会隐式地在内部进行遍历,做出相应的数据转换。


Stream 就如同一个迭代器(Iterator),单向,不可往复,数据只能遍历一次,遍历过一次后即用尽了,就好比流水从面前流过,一去不复返。


Java 的并行 API 演变历程基本如下:


1.1.0-1.4 中的 java.lang.Thread


2.5.0 中的 java.util.concurrent


3.6.0 中的 Phasers 等


4.7.0 中的 Fork/Join 框架


5.8.0 中的 Stream

Stream 的另外一大特点是,数据源本身可以是无限的(即无限流)。


对流的操作概述


流的操作类型分为两种:


Intermediate(中间操作):一个流可以后面跟随零个或多个 intermediate 操作。其目的主要是打开流,做出某种程度的数据映射/过滤,然后返回一个新的流,交给下一个操作使用。这类操作都是惰性化的(lazy),就是说,仅仅调用到这类方法,并没有真正开始流的遍历。


map (mapToInt, flatMap 等)、 filter、 distinct、 sorted、 peek、 limit、 skip、 parallel、 sequential、 unordered


    Terminal(终止操作):一个流只能有一个 terminal 操作,当这个操作执行后,流就被使用“光”了,无法再被操作。所以这必定是流的最后一个操作。Terminal 操作的执行,才会真正开始流的遍历,并且会生成一个结果,或者一个 side effect。


forEach、 forEachOrdered、 toArray、 reduce、 collect、 min、 max、 count、 anyMatch、 allMatch、 noneMatch、 findFirst、 findAny、 iterator


还有一种操作被称为 short-circuiting(短路操作)。用以指:


    对于一个 intermediate 操作,如果它接受的是一个无限流,它可以返回一个有限的新 Stream。


   对于一个 terminal 操作,如果它接受的是一个无限流,但能在有限的时间计算出结果。


anyMatch、 allMatch、 noneMatch、 findFirst、 findAny、 limit


下面汇总了Stream所有的操作:


image.png


IntStream、LongStream、DoubleStream


IntStream、LongStream、DoubleStream。当然我们也可以用 Stream、Stream >、Stream,但是 boxing 和 unboxing 会很耗时,所以特别为这三种基本数值型提供了对应的 Stream。


Java 8 中还没有提供其它数值型 Stream,因为这将导致扩增的内容较多。而常规的数值型聚合运算可以通过上面三种 Stream 进行。


数值流的构造:

IntStream.of(new int[]{1, 2, 3}).forEach(System.out::println);
IntStream.range(1, 3).forEach(System.out::println);
IntStream.rangeClosed(1, 3).forEach(System.out::println);


range,需要传入开始节点和结束节点两个参数,返回的是一个有序的LongStream。包含开始节点和结束节点两个参数之间所有的参数,间隔为1.

rangeClosed的功能和range类似。差别就是rangeClosed包含最后的结束节点,range不包含。


进阶:自己生成流(无限流)


  public static<T> Stream<T> generate(Supplier<T> s) {
        Objects.requireNonNull(s);
        return StreamSupport.stream(
                new StreamSpliterators.InfiniteSupplyingSpliterator.OfRef<>(Long.MAX_VALUE, s), false);
    }


可以自己来控制流的生成。这种情形通常用于随机数、常量的 Stream,或者需要前后元素间维持着某种状态信息的 Stream。把 Supplier 实例传递给 Stream.generate() 生成的 Stream,默认是串行(相对 parallel 而言)但无序的(相对 ordered 而言)。由于它是无限的,在管道中,必须利用 limit 之类的操作限制 Stream 大小。


生成 10 个随机整数:


/////

public static void main(String[] args) {
        Stream.generate(new Random()::nextInt).limit(10).forEach(System.out::println);
        //采用IntStream流的方式(推荐使用 逼格很高)
        IntStream.generate(() -> (int) (System.nanoTime() % 100)).
                limit(10).forEach(System.out::println);
    }

另外一种方式自己生成流(这个非常好用):


public static<T> Stream<T> iterate(final T seed, final UnaryOperator<T> f) {}


iterate 跟 reduce 操作很像,接受一个种子值,和一个 UnaryOperator(例如 f)。然后种子值成为 Stream 的第一个元素,f(seed) 为第二个,f(f(seed)) 第三个,以此类推。


如生成一个等差数列:

Stream.iterate(0, n -> n + 3).limit(10).forEach(x -> System.out.print(x + " ")); //0 3 6 9 12 15 18 21 24 27 

与 Stream.generate 相仿,在 iterate 时候管道必须有 limit 这样的操作来限制 Stream 大小。


Stream实操案例


创建流Stream


Java8 中的 Collection 接口被扩展,提供两个获取流的方法 :


   default Stream stream() : 返回一个顺序流


   default Stream parallelStream() : 返回一个并行流


由数组创建流

Java8 中的 Arrays 的静态方法 stream() 可以获取数组流 :static Stream stream(T[] array) : 返回一个流

重载形式,能够处理对应基本类型的数组IntStream/LongStream/DoubleStream :


由值创建流

可以使用静态方法 Stream.of(), 通过显示值创建一个流,它可以接收任意数量的参数:public static Stream of(T… values) : 返回一个流


由方法创建流 : 创建无限流

可以使用静态方法 Stream.iterate() 和 Stream.generate(), 创建无限流

迭代

public static Stream iterate(final T seed, final UnaryOperator f)

生成

public static Stream generate(Supplier s)


Stream的中间操作实操


多个中间操作可以连接起来形成一个流水线,除非流水线上触发终止操作,否则中间操作不会执行任何的处理。而在终止操作时一次性全部处理,称为**“惰性求值”**


   筛选与切片系列


image.png

emps.parallelStream().filter((e) -> e.getSalary() >= 5000).skip(2).forEach(System.out::println);


这块相对比较简单,就一笔带过了。


peek方法用得比较少,这里特殊介绍一下:


Stream.of("one", "two", "three", "four").peek(e -> System.out.println(e));
输出:这样不会有任何的输出;
Stream.of("one", "two", "three", "four").peek(e -> System.out.println(e)).collect(Collectors.toList());
输出:
one
two
three
four
Stream.of("one", "two", "three", "four")
    .peek(e -> System.out.println("Peeked value: " + e))
    .map(String::toUpperCase)
    .peek(e -> System.out.println("Mapped value: " + e))
    .collect(Collectors.toList());
输出:
Peeked value: one
Mapped value: ONE
Peeked value: two
Mapped value: TWO
Peeked value: three
Mapped value: THREE
Peeked value: four
Mapped value: FOUR


这个说白了。当元素被消费的时候,就会触发peek。有多少个就触发多少次。


unordered的使用案例:

public static void main(String[] args) {
        List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9);
        list.stream().forEach(System.out::print); //123456789
        System.out.println();
        //使用unordered之后输出
        list.stream().unordered().forEach(System.out::print); //123456789
    }


我们会发现,输出的顺序没有改变。所以它并不是来打乱这个顺序的。所以大家使用的时候不要误解了。正确的使用 姿势:

//使stream无序:对于 distinct() 和 limit() 等方法,如果不关心顺序,则可以使用并行:
LongStream.rangeClosed(5, 10).unordered().parallel().limit(3);
IntStream.of(14, 15, 15, 14, 12, 81).unordered().parallel().distinct();
这样使用,能提高CPU的利用率,进而提高处理的效率




相关文章
|
16天前
|
JSON Java Apache
Java基础-常用API-Object类
继承是面向对象编程的重要特性,允许从已有类派生新类。Java采用单继承机制,默认所有类继承自Object类。Object类提供了多个常用方法,如`clone()`用于复制对象,`equals()`判断对象是否相等,`hashCode()`计算哈希码,`toString()`返回对象的字符串表示,`wait()`、`notify()`和`notifyAll()`用于线程同步,`finalize()`在对象被垃圾回收时调用。掌握这些方法有助于更好地理解和使用Java中的对象行为。
|
30天前
|
算法 Java API
如何使用Java开发获得淘宝商品描述API接口?
本文详细介绍如何使用Java开发调用淘宝商品描述API接口,涵盖从注册淘宝开放平台账号、阅读平台规则、创建应用并申请接口权限,到安装开发工具、配置开发环境、获取访问令牌,以及具体的Java代码实现和注意事项。通过遵循这些步骤,开发者可以高效地获取商品详情、描述及图片等信息,为项目和业务增添价值。
63 10
|
1月前
|
存储 Java 数据挖掘
Java 8 新特性之 Stream API:函数式编程风格的数据处理范式
Java 8 引入的 Stream API 提供了一种新的数据处理方式,支持函数式编程风格,能够高效、简洁地处理集合数据,实现过滤、映射、聚合等操作。
63 6
|
1月前
|
Java API 开发者
Java中的Lambda表达式与Stream API的协同作用
在本文中,我们将探讨Java 8引入的Lambda表达式和Stream API如何改变我们处理集合和数组的方式。Lambda表达式提供了一种简洁的方法来表达代码块,而Stream API则允许我们对数据流进行高级操作,如过滤、映射和归约。通过结合使用这两种技术,我们可以以声明式的方式编写更简洁、更易于理解和维护的代码。本文将介绍Lambda表达式和Stream API的基本概念,并通过示例展示它们在实际项目中的应用。
|
2月前
|
安全 Java API
告别SimpleDateFormat:Java 8日期时间API的最佳实践
在Java开发中,处理日期和时间是一个基本而重要的任务。传统的`SimpleDateFormat`类因其简单易用而被广泛采用,但它存在一些潜在的问题,尤其是在多线程环境下。本文将探讨`SimpleDateFormat`的局限性,并介绍Java 8引入的新的日期时间API,以及如何使用这些新工具来避免潜在的风险。
42 5
|
2月前
|
开发框架 Java 关系型数据库
Java哪个框架适合开发API接口?
在快速发展的软件开发领域,API接口连接了不同的系统和服务。Java作为成熟的编程语言,其生态系统中出现了许多API开发框架。Magic-API因其独特优势和强大功能,成为Java开发者优选的API开发框架。本文将从核心优势、实际应用价值及未来展望等方面,深入探讨Magic-API为何值得选择。
74 2
|
2月前
|
安全 Java API
Java中的Lambda表达式与Stream API的高效结合####
探索Java编程中Lambda表达式与Stream API如何携手并进,提升数据处理效率,实现代码简洁性与功能性的双重飞跃。 ####
32 0
|
算法 Java 数据库
JAVA并发处理经验(四)并行模式与算法4:并行搜索模式
一、前言 在并行搜索模式中,主要是内存数据很大,需要查找的情况。因为我们通常需要查找数据库返回的结果据很少,在几百条以内。
1145 0
|
4天前
|
监控 Java
java异步判断线程池所有任务是否执行完
通过上述步骤,您可以在Java中实现异步判断线程池所有任务是否执行完毕。这种方法使用了 `CompletionService`来监控任务的完成情况,并通过一个独立线程异步检查所有任务的执行状态。这种设计不仅简洁高效,还能确保在大量任务处理时程序的稳定性和可维护性。希望本文能为您的开发工作提供实用的指导和帮助。
41 17
|
14天前
|
Java
Java—多线程实现生产消费者
本文介绍了多线程实现生产消费者模式的三个版本。Version1包含四个类:`Producer`(生产者)、`Consumer`(消费者)、`Resource`(公共资源)和`TestMain`(测试类)。通过`synchronized`和`wait/notify`机制控制线程同步,但存在多个生产者或消费者时可能出现多次生产和消费的问题。 Version2将`if`改为`while`,解决了多次生产和消费的问题,但仍可能因`notify()`随机唤醒线程而导致死锁。因此,引入了`notifyAll()`来唤醒所有等待线程,但这会带来性能问题。
Java—多线程实现生产消费者
下一篇
开通oss服务