Flink-转换算子
创始人
2025-06-01 16:11:58

 基本转换算子

        map(映射)

        filter(过滤)

        flatMap(扁平映射)

 聚合算子

        keyBy(按键分区)

        简单聚合

        reduce(归约聚合)

UDF介绍 

        函数类

        富函数类


       数据源读入数据之后,我们就可以使用各种转换算子,将一个或多个 DataStream 转换为 新的 DataStream。一个 Flink 程序的核心,其实就是所有的转换操作,它们决 定了处理的业务逻辑。 

 基本转换算子

        map(映射)

        主要用于将数据流中的数据进行转换,形成新 的数据流。

        我们只需要基于 DataStrema 调用 map()方法就可以进行转换处理。方法需要传入的参数是 接口 MapFunction 的实现;返回值类型还是 DataStream,不过泛型(流中的元素类型)可能改 变。

case class Event(user: String, url: String, timestamp: Long)
object test {def main(args: Array[String]): Unit = {val env: StreamExecutionEnvironment = StreamExecutionEnvironment.getExecutionEnvironment//设置全局并行度env.setParallelism(1)//创建当前样例类Event的对象val stream: DataStream[Event] = env.fromElements(Event("张三", "01", 1000L),Event("李四", "04", 2000L),Event("王五", "01", 6000L),Event("赵六", "03", 1000L))//转换需求:提取用户名//实现方式1:使用匿名函数stream.map( _.user).print("方式1:")//实现方式2:实现MapFunction接口stream.map(new UserEX).print("方式2:")//执行env.execute()}//定义实现接口类class UserEX extends MapFunction[Event,String]{override def map(t: Event): String = t.user}
}

        filter(过滤)

        filter()转换操作,顾名思义是对数据流执行一个过滤,通过一个布尔条件表达式设置过滤 条件,对于每一个流内元素进行判断,若为 true 则元素正常输出,若为 false 则元素被过滤掉

        进行 filter()转换之后的新数据流的数据类型与原数据流是相同的。filter()转换需要传入的 参数需要实现 FilterFunction 接口,而 FilterFunction 内要实现 filter()方法,就相当于一个返回 布尔类型的条件表达式。

case class Event(user: String, url: String, timestamp: Long)
object f5 {def main(args: Array[String]): Unit = {val env: StreamExecutionEnvironment = StreamExecutionEnvironment.getExecutionEnvironment//设置全局并行度env.setParallelism(1)//创建当前样例类Event的对象val stream: DataStream[Event] = env.fromElements(Event("张三", "01", 1000L),Event("李四", "04", 2000L),Event("王五", "01", 6000L),Event("赵六", "03", 1000L))//过滤出用户名为王五的点击事件//方式1:使用匿名函数stream.filter(_.user == "王五").print("方式1:")//方式2:使用匿名函数stream.filter(new UserF).print("方式2:")//执行env.execute()}//定义实现接口类class UserF extends FilterFunction[Event] {override def filter(t: Event): Boolean = t.user == "王五"}
}

        flatMap(扁平映射)

        flatMap()操作又称为扁平映射,主要是将数据流中的整体(一般是集合类型)拆分成一个 一个的个体使用。消费一个元素,可以产生 0 到多个元素。flatMap()可以认为是“扁平化” (flatten)和“映射”(map)两步操作的结合,也就是先按照某种规则对数据进行打散拆分, 再对拆分后的元素做转换处理。

        同 map()一样,flatMap()也可以使用 Lambda 表达式或者 FlatMapFunction 接口实现类的方 式来进行传参,返回值类型取决于所传参数的具体逻辑,可以与原数据流的数据类型相同,也 可以不同。

case class Event(user: String, url: String, timestamp: Long)
object f5 {def main(args: Array[String]): Unit = {val env: StreamExecutionEnvironment = StreamExecutionEnvironment.getExecutionEnvironment//设置全局并行度env.setParallelism(1)//创建当前样例类Event的对象val stream: DataStream[Event] = env.fromElements(Event("张三", "01", 1000L),Event("李四", "04", 2000L),Event("王五", "01", 6000L),Event("赵六", "03", 1000L))//测试灵活的输出形式:如果点击用户为李四 则输出李四stream.flatMap(new UserF).print()//执行env.execute()}//定义实现接口类class UserF extends FlatMapFunction[Event,String] {override def flatMap(t: Event, collector: Collector[String]): Unit = {//如果当前数据是Mary的点击事件则直接输出userif(t.user == "李四"){collector.collect(t.user)}}}
}

 聚合算子

        keyBy(按键分区)

        对于 Flink 而言,DataStream 是没有直接进行聚合的 API 的。因为我们对海量数据做聚合 肯定要进行分区并行处理,这样才能提高效率。所以在 Flink 中,要做聚合,需要先进行分区; 这个操作就是通过 keyBy()来完成的。

        keyBy()是聚合前必须要用到的一个算子。keyBy()通过指定键(key),可以将一条流从逻 辑上划分成不同的分区(partitions)。这里所说的分区,其实就是并行处理的子任务,也就对 应着任务槽(task slots)。

        基于不同的 key,流中的数据将被分配到不同的分区中去,如图 5-8 所示;这样一来,所 有具有相同的 key 的数据,都将被发往同一个分区,那么下一步算子操作就将会在同一个 slot 中进行处理了。

case class Event(user: String, url: String, timestamp: Long)
object f6 {def main(args: Array[String]): Unit = {val env: StreamExecutionEnvironment = StreamExecutionEnvironment.getExecutionEnvironment//设置全局并行度env.setParallelism(1)//创建当前样例类Event的对象val stream: DataStream[Event] = env.fromElements(Event("张三", "01", 1000L),Event("李四", "04", 2000L),Event("王五", "01", 6000L),Event("赵六", "03", 1000L))//按键分区//1、接口实现stream.keyBy( new MyKeyb ).print()//匿名函数实现stream.keyBy( k => k.user)//执行env.execute()}//定义实现接口类class MyKeyb extends  KeySelector[Event,String]{override def getKey(in: Event): String = in.user}
}

        简单聚合

        有了按键分区的数据流 KeyedStream,我们就可以基于它进行聚合操作了。Flink 为我们 内置实现了一些最基本、最简单的聚合 API,主要有以下几种: 

  • sum():在输入流上,对指定的字段做叠加求和的操作。
  • min():在输入流上,对指定的字段求最小值。
  • max():在输入流上,对指定的字段求最大值。
  • minBy():与 min()类似,在输入流上针对指定字段求最小值。不同的是,min()只计 算指定字段的最小值,其他字段会保留最初第一个数据的值;而 minBy()则会返回包 含字段最小值的整条数据。 
  • maxBy():与 max()类似,在输入流上针对指定字段求最大值。两者区别与 min()/minBy()完全一致。
object f6 {def main(args: Array[String]): Unit = {val env: StreamExecutionEnvironment = StreamExecutionEnvironment.getExecutionEnvironment//设置全局并行度env.setParallelism(1)//创建当前样例类Event的对象val stream: DataStream[Event] = env.fromElements(Event("张三", "01", 1000L),Event("张三", "01", 6000L),Event("李四", "04", 2000L),Event("李四", "05", 6000L),Event("王五", "01", 1000L),Event("赵六", "03", 1000L))//使用 user 作为分组的字段,并计算最大的时间戳stream.keyBy( new MyKeyb ).maxBy("timestamp").print()//执行env.execute()}//定义实现接口类class MyKeyb extends  KeySelector[Event,String]{override def getKey(in: Event): String = in.user}
}

        reduce(归约聚合)

与简单聚合类似,reduce()操作也会将 KeyedStream 转换为 DataStream。它不会改变流的 元素数据类型,所以输出类型和输入类型是一样的。

         调用 KeyedStream 的 reduce()方法时,需要传入一个参数,实现 ReduceFunction 接口。接 口在源码中的定义如下:

case class Event(user: String, url: String, timestamp: Long)
object f6 {def main(args: Array[String]): Unit = {val env: StreamExecutionEnvironment = StreamExecutionEnvironment.getExecutionEnvironment//设置全局并行度env.setParallelism(1)//创建当前样例类Event的对象val stream: DataStream[Event] = env.fromElements(Event("张三", "01", 1000L),Event("张三", "01", 6000L),Event("李四", "04", 2000L),Event("李四", "05", 6000L),Event("王五", "01", 1000L),Event("赵六", "03", 1000L))//归约聚合:统计用户点击页面的数量,提取当前最活跃的用户stream.map( d => (d.user,1L)).keyBy(_._1).reduce( new MyS) //统计每个用户的活跃度.keyBy( d => true) //将所有的数据按照同样的key分到同一个组中.reduce((a,b) => if(b._2 > a._2) b else a) //选取当前最活跃的用户.print() //输出结果//执行env.execute()}//定义实现接口类class MyS extends ReduceFunction[(String,Long)]{override def reduce(t: (String, Long), t1: (String, Long)): (String, Long) = (t._1,t._2+t1._2)}
}

UDF介绍 

        函数类

对于大部分操作而言,都需要传入一个用户自定义函数(UDF),实现相关操作的接口, 来完成处理逻辑的定义。Flink 暴露了所有 UDF 函数的接口,具体实现方式为接口或者抽象类, 例如 MapFunction、FilterFunction、ReduceFunction 等。所以最简单直接的方式,就是自定义一个函数类,实现对应的接口。

下面例子实现了 FilterFunction 接口,用来筛选 url 中包含“01”的内容:

object f7 {def main(args: Array[String]): Unit = {val env: StreamExecutionEnvironment = StreamExecutionEnvironment.getExecutionEnvironment//设置全局并行度env.setParallelism(1)//创建当前样例类Event的对象val stream: DataStream[Event] = env.fromElements(Event("张三", "01", 1000L),Event("张三", "01", 6000L),Event("李四", "04", 2000L),Event("李四", "05", 6000L),Event("王五", "01", 1000L),Event("赵六", "03", 1000L))//测试自定义函数类UDF的用法:筛选url中包含关键字“01”的事件//1、实现一个自定义的函数类stream.filter( new MyFFun).print()//2、使用匿名类 筛选url中包含关键字“04”的事件stream.filter( new FilterFunction[Event] {override def filter(t: Event): Boolean = t.url.contains("04")}).print()//3、使用匿名函数stream.filter(d => d.url.contains("05"))//执行env.execute()}//定义实现接口类class MyFFun extends FilterFunction[Event]{override def filter(t: Event): Boolean = t.url.contains("01")}
}

        富函数类

        “富函数类”也是 DataStream API 提供的一个函数类的接口,所有的 Flink 函数类都有其 Rich 版本。富函数类一般是以抽象类的形式出现的。例如:RichMapFunction、RichFilterFunction、 RichReduceFunction 等。 

        与常规函数类的不同主要在于,富函数类可以获取运行环境的上下文,并拥有一些生命周期方法,所以可以实现更复杂的功能,典型的生命周期方法有:

  • open()方法,是 Rich Function 的初始化方法,也就是会开启一个算子的生命周期。当 一个算子的实际工作方法例如 map()或者 filter()方法被调用之前,open()会首先被调 用。所以像文件 IO 流的创建,数据库连接的创建,配置文件的读取等等这样一次性 的工作,都适合在 open()方法中完成。
  • close()方法,是生命周期中的最后一个调用的方法,类似于解构方法。一般用来做一 些清理工作。 
object f8 {def main(args: Array[String]): Unit = {val env: StreamExecutionEnvironment = StreamExecutionEnvironment.getExecutionEnvironment//设置全局并行度env.setParallelism(1)//创建当前样例类Event的对象val stream: DataStream[Event] = env.fromElements(Event("张三", "01", 1000L),Event("张三", "01", 6000L),Event("李四", "04", 2000L),Event("李四", "05", 6000L),Event("王五", "01", 1000L),Event("赵六", "03", 1000L))//自定义一个RichMapFunction 测试富函数类的功能stream.map(new MyRichMap).print()//执行env.execute()}//定义实现接口类class MyRichMap extends RichMapFunction[Event,Long]{override def open(parameters: Configuration): Unit = {println("索引号为:"+ getRuntimeContext.getIndexOfThisSubtask + "的任务开始")}override def map(in: Event): Long = in.timestamp //输出一个长整型的时间戳override def close(): Unit = {println("索引号为:"+ getRuntimeContext.getIndexOfThisSubtask + "的任务结束")}}
}

         适合使用的场景

class MyFlatMap extends RichFlatMapFunction[IN,OUT]{override def open(parameters: Configuration): Unit = {// 做一些初始化工作// 例如建立一个和 MySQL 的连接}override def flatMap(value: IN, out: Collector[OUT]): Unit = {// 对数据库进行读写}override def close(): Unit = {// 清理工作,关闭和 MySQL 数据库的连接。}
}

 

相关内容

热门资讯

【实验报告】实验一 图像的... 实验目的熟悉Matlab图像运算的基础——矩阵运算;熟悉图像矩阵的显示方法࿰...
MATLAB | 全网最详细网... 一篇超超超长,超超超全面网络图绘制教程,本篇基本能讲清楚所有绘制要点&#...
大模型落地比趋势更重要,NLP... 全球很多人都开始相信,以ChatGPT为代表的大模型,将带来一场NLP领...
Linux学习之端口、网络协议... 端口:设备与外界通讯交流的出口 网络协议:   网络协议是指计算机通信网...
kuernetes 资源对象分... 文章目录1. pod 状态1.1 容器启动错误类型1.2 ImagePullBackOff 错误1....
STM32实战项目-数码管 程序实现功能: 1、上电后,数码管间隔50ms计数; 2、...
TM1638和TM1639差异... TM1638和TM1639差异说明 ✨本文不涉及具体的单片机代码驱动内容,值针对芯...
Qt+MySql开发笔记:Qt... 若该文为原创文章,转载请注明原文出处 本文章博客地址:https://h...
Java内存模型中的happe... 第29讲 | Java内存模型中的happen-before是什么? Java 语言...
《扬帆优配》算力概念股大爆发,... 3月22日,9股封单金额超亿元,工业富联、鸿博股份、鹏鼎控股分别为3.0...
CF1763D Valid B... CF1763D Valid Bitonic Permutations 题目大意 拱形排列࿰...
SQL语法 DDL、DML、D... 文章目录1 SQL通用语法2 SQL分类3 DDL 数据定义语言3.1 数据库操作3.2 表操作3....
文心一言 VS ChatGPT... 3月16号,百度正式发布了『文心一言』,这是国内公司第一次发布类Chat...
CentOS8提高篇5:磁盘分...        首先需要在虚拟机中模拟添加一块新的硬盘设备,然后进行分区、格式化、挂载等...
Linux防火墙——SNAT、... 目录 NAT 一、SNAT策略及作用 1、概述 SNAT应用环境 SNAT原理 SNAT转换前提条...
部署+使用集群的算力跑CPU密... 我先在开头做一个总结,表达我最终要做的事情和最终环境是如何的,然后我会一...
Uploadifive 批量文... Uploadifive 批量文件上传_uploadifive 多个上传按钮_asing1elife的...
C++入门语法基础 文章目录:1. 什么是C++2. 命名空间2.1 域的概念2.2 命名...
2023年全国DAMA-CDG... DAMA认证为数据管理专业人士提供职业目标晋升规划,彰显了职业发展里程碑及发展阶梯定义...
php实现助记词转TRX,ET... TRX助记词转地址网上都是Java,js或其他语言开发的示例,一个简单的...
【分割数据集操作集锦】毕设记录 1. 按要求将CSV文件转成json文件 有时候一些网络模型的源码会有data.json这样的文件里...
Postman接口测试之断言 如果你看文字部分还是不太理解的话,可以看看这个视频,详细介绍postma...
前端学习第三阶段-第4章 jQ... 4-1 jQuery介绍及常用API导读 01-jQuery入门导读 02-JavaScri...
4、linux初级——Linu... 目录 一、用CRT连接开发板 1、安装CRT调试工具 2、连接开发板 3、开机后ctrl+c...
Urban Radiance ... Urban Radiance Fields:城市辐射场 摘要:这项工作的目标是根据扫描...
天干地支(Java) 题目描述 古代中国使用天干地支来记录当前的年份。 天干一共有十个,分别为:...
SpringBoot雪花ID长... Long类型精度丢失 最近项目中使用雪花ID作为主键,雪花ID是19位Long类型数...
对JSP文件的理解 JSP是java程序。(JSP本质还是一个Servlet) JSP是&#...
【03173】2021年4月高... 一、单向填空题1、大量应用软件开发工具,开始于A、20世纪70年代B、20世纪 80年...
LeetCode5.最长回文子... 目录题目链接题目分析解题思路暴力中心向两边拓展搜索 题目链接 链接 题目分析 简单来说࿰...