1.官方文档
Flink中的算子,是对 DataStream 进行操作,返回一个新的 DataStream 的过程。Transformation 过程,是将一个或多个 DataStream 转换为新的 DataStream,可以将多个转换组合成复杂的数据流拓扑
Transformation:指数据转换的各种操作。有 map / flatMap / filter / keyBy / reduce / fold / aggregations / window / windowAll / union / window join / split / select / project 等,操作有很多中凡是,可以将数据转换计算成你想要的数据。
在Flink中,有多种不同的 DataStream 类型,他们之间就是使用各种算子来进行转换的。如下图所示:
2.官方文档
附:Flink算子Transformation介绍,我是官方文档(英文版)
3.常用算子介绍
本文使用 JDK8新特性:Lambda 表达式
来介绍 Flink 中常用算子使用。如果你还不了解 JDK8 新特性,请点击了解了解: 我是链接 。
如需了解 Lambda表达式的使用,你也可以点击以下链接具体了解:JDK8新特性:Lambda表达式
3.1 map()
映射函数
。即:取出一个元素,根据规则处理后,并产生一个元素。可以用来做一些数据清洗的工作。
类型转换:
DataStream → DataStream
场景:
使用 java.util.Collection 创建一个数据流,并将数据流中的数据 * 2,并输出。
/**
* TODO map()方法操作
*
* @author liuzebiao
* @Date 2020-2-8 13:49
*/
public class MapDemo {
public static void main(String[] args) throws Exception {
StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
//创建数据流
DataStreamSource<Integer> dataStream = env.fromElements(1, 2, 3, 4, 5);
//将Stream流中的数据 *2
SingleOutputStreamOperator<Integer> operator = dataStream.map(num -> num * 2).returns(Types.INT);
operator.print();
env.execute("MapDemo");
}
}
3.2 flatMap()
拆分压平
。即:取出一个元素,并产生零个、一个或多个元素。
flatMap 和 map 方法的使用相似,但是因为一般 Java 方法的返回值结果都是一个,引入 flatMap 后,我们可以将处理后的多个结果放到一个 Collections 集合中(类似于返回多个结果)。
类型转换:
DataStream → DataStream
场景:
使用 java.util.Collection 创建一个数据流,并将数据流中以 “S” 开头的数据返回。
/**
* TODO flatMap()方法操作
*
* @author liuzebiao
* @Date 2020-2-9 10:49
*/
public class FlatMapDemo {
public static void main(String[] args) throws Exception {
StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
//创建数据流
DataStreamSource<String> dataStreamSource = env.fromElements("Hadoop Flink Storm HBase", "Spark Tomcat Spring MyBatis", "Sqoop Flume Docker K8S Scala");
//将Stream流中以 "S" 开头的数据,输出到 Collectior 集合中
SingleOutputStreamOperator<String> streamOperator = dataStreamSource.flatMap((String line, Collector<String> out) -> {
Arrays.stream(line.split(" ")).forEach( str ->{
if (str.startsWith("S")) {
out.collect(str);
}
});
}).returns(Types.STRING);
streamOperator.print();
env.execute("FlatMapDemo");
}
}
3.3 filter()
过滤
。即:为取出的每个元素进行规则判断(返回true/false),并保留该函数返回 true 的数据。
类型转换:
DataStream → DataStream
场景:
使用 java.util.Collection 创建一个数据流,并将数据流中以 “S” 开头的数据返回。
/**
* TODO filter()方法操作
*
* @author liuzebiao
* @Date 2020-2-9 11:26
*/
public class FilterDemo {
public static void main(String[] args) throws Exception {
StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
//创建数据流
DataStreamSource<String> dataStreamSource = env.fromElements("Hadoop", "Spark", "Tomcat", "Storm", "Flink", "Docker", "Hive", "Sqoop" );
//将Stream流中以 "S" 开头的数据,输出
SingleOutputStreamOperator<String> streamOperator = dataStreamSource.filter(str -> str.startsWith("S")).returns(Types.STRING);
streamOperator.print();
env.execute("FilterDemo");
}
}
3.4 keyBy()
按 key 进行分组,key相同的(一定)会进入到同一个组中
。具有相同键的所有记录都会分配给同一分区。在内部,keyBy() 是通过哈希分区实现的,有多种指定密钥的方法。
此转换返回一个
KeyedStream
。使用 keyBy 进行分区,分为以下下两种情况:
- POJO类型:以 “属性名” 进行分组(
属性名错误
或不存在
,会提示错误)- Tuple(元组)类型:以“0、1、2”等进行分组(角标从0开始)
eg:
dataStream.keyBy(“someKey”) // Key by field “someKey”
dataStream.keyBy(0) // Key by the first element of a Tuple
keyBy() ,也支持以多个字段进行分组。 例如:①keyBy(0,1):Tuple形式以第1和第2个字段进行分组
②keyBy(“province”,“city”):POJO形式以"province"和"city"两个字段进行分组
。多字段分组使用方法,与单个字段分组类似。单个字段分组实例Demo,请往下查看例子。
类型转换:
DataStream → KeyedStream
场景:
通过 Socket 方式,实时获取输入的数据,并对数据流中的单词进行分组求和计算(如何通过Socket输入数据,请参考:Java编写实时任务WordCount)
3.4.1 POJO(实体类)方式 keyBy(使用属性 keyBy
)
/**
* TODO WordCount POJO实体类
*
* @author liuzebiao
* @Date 2020-2-9 15:04
*/
public class WordCount {
public String word;
public int count;
public WordCount() {
}
public WordCount(String word, int count) {
this.word = word;
this.count = count;
}
//of()方法,用来生成 WordCount 类(Flink源码均使用of()方法形式,省去每次new操作。诸如:Tuple2.of())
public static WordCount of(String word,int count){
return new WordCount(word, count);
}
@Override
public String toString() {
return "WordCount{" +
"word='" + word + '\'' +
", count=" + count +
'}';
}
}
/**
* TODO keyBy()方法操作(POJO实体类形式)
*
* @author liuzebiao
* @Date 2020-2-9 15:06
*/
public class KeyByDemo {
public static void main(String[] args) throws Exception {
StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
//通过Socket实时获取数据
DataStreamSource<String> lines = env.socketTextStream("localhost", 8888);
//将数据转换成 POJO 实体类形式
SingleOutputStreamOperator<WordCount> streamOperator = lines.flatMap((String line, Collector<WordCount> out) -> {
Arrays.stream(line.split(" ")).forEach(str -> out.collect(WordCount.of(str, 1)));
}).returns(WordCount.class);
//keyBy()以属性名:word 进行分组
KeyedStream<WordCount, Tuple> keyedStream = streamOperator.keyBy("word");
//sum()以属性名:count 进行求和
SingleOutputStreamOperator<WordCount> summed = keyedStream.sum("count");
summed.print();
env.execute("KeyByDemo");
}
}
返回结果:
7> WordCount{word='Storm', count=1}
8> WordCount{word='Flink', count=1}
8> WordCount{word='Spark', count=1}
8> WordCount{word='Docker', count=1}
7> WordCount{word='Scala', count=1}
8> WordCount{word='Flink', count=2}
8> WordCount{word='Flink', count=3}
8> WordCount{word='Flink', count=4}
8> WordCount{word='Spark', count=2}
3.4.2 Tuple(元组)方式 keyBy(使用下标 keyBy
)
/**
* TODO keyBy()方法操作(Tuple 元组形式)
*
* @author liuzebiao
* @Date 2020-2-9 14:38
*/
public class KeyByDemo {
public static void main(String[] args) throws Exception {
StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
//通过Socket实时获取数据
DataStreamSource<String> dataSource = env.socketTextStream("localhost", 8888);
//将数据转换成元组(word,1)形式
SingleOutputStreamOperator<Tuple2<String, Integer>> streamOperator = dataSource.flatMap((String lines, Collector<Tuple2<String, Integer>> out) -> {
Arrays.stream(lines.split(" ")).forEach(word -> out.collect(Tuple2.of(word, 1)));
}).returns(Types.TUPLE(Types.STRING, Types.INT));
//keyBy()以下标的形式,进行分组
KeyedStream<Tuple2<String, Integer>, Tuple> keyedStream = streamOperator.keyBy(0);
//sum()以下标的形式,对其进行求和
SingleOutputStreamOperator<Tuple2<String, Integer>> summed = keyedStream.sum(1);
summed.print();
env.execute("KeyByDemo");
}
}
返回结果:
8> (Flink,1)
8> (Spark,1)
7> (Storm,1)
8> (Docker,1)
7> (Scala,1)
8> (Flink,2)
8> (Flink,3)
8> (Flink,4)
8> (Spark,2)
3.5 reduce()
归并操作
。如果需要将数据流中的所有数据,归纳得到一个数据的情况,可以使用 reduce() 方法。如果需要对数据流中的数据进行求和操作
、求最大/最小值
等(都是归纳为一个数据的情况),此处就可以用到 reduce() 方法
reduce() 返回单个的结果值,并且 reduce 操作每处理一个元素总是会创建一个新的值。常用的聚合操作例如 min()、max() 等都可使用 reduce() 方法实现。Flink 中未实现的 average(平均值), count(计数)
等操作,也都可以通过 reduce()方法实现。
类型转换:
KeyedStream → DataStream
场景:
通过 Socket 方式,实时获取输入的数据,并对数据流中的单词进行分组,分组后进行 count 计数操作。(如何通过Socket输入数据,请参考:Java编写实时任务WordCount)
/**
* TODO reduce()方法操作
*
* @author liuzebiao
* @Date 2020-2-9 17:11
*/
public class ReduceDemo {
public static void main(String[] args) throws Exception {
StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
//通过Socket实时获取数据
DataStreamSource<String> dataStreamSource = env.socketTextStream("localhost", 8888);
SingleOutputStreamOperator<Tuple2<String, Integer>> streamOperator = dataStreamSource.map(str -> Tuple2.of(str, 1)).returns(Types.TUPLE(Types.STRING,Types.INT));
//keyBy() Tuple元组以下标的形式,进行分组
KeyedStream<Tuple2<String, Integer>, Tuple> keyedStream = streamOperator.keyBy(0);
//对分组后的数据进行 reduce() 操作
//old,news 为两个 Tuple2<String,Integer>类型(通过f0,f1可以获得相对应下标的值)
SingleOutputStreamOperator<Tuple2<String, Integer>> count = keyedStream.reduce((old, news) -> {
old.f1 += news.f1;
return old;
}).returns(Types.TUPLE(Types.STRING, Types.INT));
count.print();
env.execute("ReduceDemo");
}
}
返回结果:
8> (hadoop,1)
7> (flink,1)
8> (hadoop,2)
8> (hadoop,3)
8> (hadoop,4)
8> (hadoop,5)
7> (flink,2)
3.6 sum()、min() 、minBy()、max()、maxBy()
sum():求和
min():返回最小值
max():返回最大值
(指定的field是最小,但不是最小的那条记录)
minBy(): 返回最小值的元素
maxBy(): 返回最大值的元素
(获取的最小值,同时也是最小值的那条记录)
类型转换:
KeyedStream → DataStream
场景:
通过 Socket 方式,实时获取输入的数据,并对数据流中的单词进行分组,分组后进行 sum() 求和计数(min()、max() 方法,同 sum() 的使用相同)。请参考:Java编写实时任务WordCount
3.7 fold()
一个有初始值的分组数据流的滚动折叠操作。合并当前元素和前一次折叠操作的结果,并产生一个新的值。
fold() 方法只是对分组中的数据进行折叠操作。比如有 3 个分组,然后我们通过如下代码来完成对分组中数据的折叠操作。分组如下:
组1:【11,22,33,44,55】
组2:【88】
组3:【98,99】
代码如下:
DataStream<String> result =
keyedStream.fold("start", new FoldFunction<Integer, String>() {
@Override
public String fold(String current, Integer value) {
return current + "-" + value;
}
});
最终折叠后的结果为:
组1:【start-11、start-11-22、start-11-22-33、start-11-22-33-44、start-11-22-33-44-55】
组2:【start-88】
组3:【start-98、start-98-99】
总结:
fold() 方法,只会对本分组中的数据进行折叠操作,如果当前分组结束开始操作下一个分组,那么折叠操作将会重新开始。
附:Demo
/**
* TODO fold()方法操作
*
* @author liuzebiao
* @Date 2020-2-10 9:00
*/
public class FoldDemo {
public static void main(String[] args) throws Exception {
StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
DataStreamSource<Integer> source = env.fromElements(11, 11, 11, 22, 33, 44, 55);
SingleOutputStreamOperator<Tuple2<Integer, Integer>> streamOperator = source.map(num ->Tuple2.of(num,1)).returns(Types.TUPLE(Types.INT,Types.INT));
KeyedStream<Tuple2<Integer, Integer>, Tuple> keyedStream = streamOperator.keyBy(0);
DataStream<String> result = keyedStream.fold("start",(current,tuple) ->current + "-" + tuple.f0).returns(Types.STRING);
result.print();
env.execute("FoldDemo");
}
}
分组后:
组1:【11, 11, 11】
组2:【22】
组3:【33】
组4:【44】
组5:【55】
返回结果:
7> start-11
7> start-11-11
7> start-11-11-11
3> start-22
2> start-33
1> start-44
8> start-55
Flink常用算子Transformation介绍,介绍到此为止
文章都是博主精心编写,如果本文对你有所帮助,那就给我点个赞呗 _
End