Spark常用算子-KeyValue数据类型的算子
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 | package com.test; import java.util.ArrayList; import java.util.List; import java.util.Map; import org.apache.spark.Partitioner; import org.apache.spark.SparkConf; import org.apache.spark.api.java.JavaPairRDD; import org.apache.spark.api.java.JavaRDD; import org.apache.spark.api.java.JavaSparkContext; import org.apache.spark.api.java.Optional; import org.apache.spark.api.java.function.Function; import org.apache.spark.api.java.function.Function2; import org.apache.spark.api.java.function.PairFunction; import org.apache.spark.api.java.function.VoidFunction; import scala.Tuple2; /** * KeyValue数据类型的Transformation算子 * @author FengZhen * */ public class SparkKeyValue{ public static void main(String[] args){ //SparkConf conf = new SparkConf().setAppName(SparkKeyValue.class.getName()).setMaster("local[2]"); SparkConf conf = new SparkConf().setAppName(SparkKeyValue. class .getName()); JavaSparkContext sc = new JavaSparkContext(conf); // 数据 JavaRDD<String> ds = sc.textFile( "hdfs://bjqt/data/labeldata/datalabel.csv" ); /** * 获取身份证:姓名 */ JavaPairRDD<String, String> pairRDD = ds.mapToPair( new PairFunction<String, String, String>() { @Override public Tuple2<String, String> call(String t) throws Exception { String line = t.replace( "‘" , "" ); String[] lines = line.split( "," ); return new Tuple2<String, String>(lines[1], lines[0]); } }); Map<String, String> pairMap = pairRDD.collectAsMap(); System. out .println( "pairMap=" + pairMap); /** *一、输入分区与输出分区一对一 *15、mapValues算子 */ /** * 15、mapValues算子 * mapValues :针对(Key, Value)型数据中的 Value 进行 Map 操作,而不对 Key 进行处理。 * 获取身份证号:姓氏 */ JavaPairRDD<String, String> firstNameRDD = pairRDD.mapValues( new Function<String, String>() { @Override public String call(String v1) throws Exception { return v1.substring(0, 1); } }); Map<String, String> firstNameMap = firstNameRDD.collectAsMap(); System. out .println( "firstNameMap=" + firstNameMap); /** * 二、对单个RDD或两个RDD聚集 单个RDD聚集 16、combineByKey算子 17、reduceByKey算子 18、partitionBy算子 两个RDD聚集 19、Cogroup算子 */ /** * 16、combineByKey算子 * 下面代码为 combineByKey 函数的定义: combineByKey[C](createCombiner:(V) C, mergeValue:(C, V) C, mergeCombiners:(C, C) C, partitioner:Partitioner, mapSideCombine:Boolean=true, serializer:Serializer=null):RDD[(K,C)] 说明: createCombiner: V => C, C 不存在的情况下,比如通过 V 创建 seq C。 mergeValue: (C, V) => C,当 C 已经存在的情况下,需要 merge,比如把 item V 加到 seq C 中,或者叠加。 mergeCombiners: (C, C) => C,合并两个 C。 partitioner: Partitioner, Shuff le 时需要的 Partitioner。 mapSideCombine : Boolean = true,为了减小传输量,很多 combine 可以在 map 端先做,比如叠加,可以先在一个 partition 中把所有相同的 key 的 value 叠加, 再 shuffle。 serializerClass: String = null,传输需要序列化,用户可以自定义序列化类: 例如,相当于将元素为 (Int, Int) 的 RDD 转变为了 (Int, Seq[Int]) 类型元素的 RDD createCombiner: V => C ,这个函数把当前的值作为参数,此时我们可以对其做些附加操作(类型转换)并把它返回 (这一步类似于初始化操作) mergeValue: (C, V) => C,该函数把元素V合并到之前的元素C(createCombiner)上 (这个操作在每个分区内进行) mergeCombiners: (C, C) => C,该函数把2个元素C合并 (这个操作在不同分区间进行) 获取相同姓氏的身份证号集合 */ JavaPairRDD<String, String> namePairRDD = firstNameRDD.mapToPair( new PairFunction<Tuple2<String,String>, String, String>() { @Override public Tuple2<String, String> call(Tuple2<String, String> t) throws Exception { return new Tuple2<String, String>(t._2, t._1); } }); Map<String, String> namePairMap = namePairRDD.collectAsMap(); System. out .println( "namePairMap=" +namePairMap); JavaPairRDD<String, List<String>> combineByKeyRDD = namePairRDD.combineByKey( new Function<String, List<String>>() { @Override public List<String> call(String v1) throws Exception { List<String> list = new ArrayList<String>(); list.add(v1); return list; } }, new Function2<List<String>, String, List<String>>(){ @Override public List<String> call(List<String> v1, String v2) throws Exception { v1.add(v2); return v1; } }, new Function2<List<String>, List<String>, List<String>>() { @Override public List<String> call(List<String> v1, List<String> v2) throws Exception { List<String> list = new ArrayList<String>(); list.addAll(v1); list.addAll(v2); return list; } }); Map<String, List<String>> combineByKeyMap = combineByKeyRDD.collectAsMap(); System. out .println( "combineByKeyMap=" +combineByKeyMap); /** * 17、reduceByKey算子 * reduceByKey 是比 combineByKey 更简单的一种情况,只是两个值合并成一个值,( Int, Int V)to (Int, Int C),比如叠加。所以 createCombiner reduceBykey 很简单,就是直接返回 v,而 mergeValue和 mergeCombiners 逻辑是相同的,没有区别。 函数实现: def reduceByKey(partitioner: Partitioner, func: (V, V) => V): RDD[(K, V)] = { combineByKey[V]((v: V) => v, func, func, partitioner) } 计算每个姓氏对应的人数 */ JavaPairRDD<String, Integer> firstNameCountPairRDD = namePairRDD.mapToPair( new PairFunction<Tuple2<String,String>, String, Integer>() { @Override public Tuple2<String, Integer> call(Tuple2<String, String> t) throws Exception { return new Tuple2<String, Integer>(t._1, 1); } }); JavaPairRDD<String, Integer> reduceByKeyRDD = firstNameCountPairRDD.reduceByKey( new Function2<Integer, Integer, Integer>() { @Override public Integer call(Integer v1, Integer v2) throws Exception { return v1+v2; } }); Map<String, Integer> reduceByKeyMap = reduceByKeyRDD.collectAsMap(); System. out .println( "reduceByKeyMap=" +reduceByKeyMap); /** * 18、partitionBy算子(按key分) * partitionBy函数对RDD进行分区操作。 函数定义如下。 partitionBy(partitioner:Partitioner) 如果原有RDD的分区器和现有分区器(partitioner)一致,则不重分区,如果不一致,则相当于根据分区器生成一个新的ShuffledRDD。 */ JavaPairRDD<String, String> idNamePairRDD = namePairRDD.mapToPair( new PairFunction<Tuple2<String,String>, String, String>() { @Override public Tuple2<String, String> call(Tuple2<String, String> t) throws Exception { return new Tuple2<String, String>(t._2, t._1); } }); //男女分区 JavaPairRDD<String, String> partitionRDD = idNamePairRDD.partitionBy( new Partitioner() { //分区数量 @Override public int numPartitions() { return 2; } //根据分区规则指定分区 @Override public int getPartition(Object arg0) { String idCard = (String)arg0; char genderSign = idCard.charAt(idCard.length()-2); String genderStr = String.valueOf(genderSign); Integer genderInt = Integer.parseInt(genderStr); if (genderInt%2 == 0) { return 0; } else { return 1; } } }); Map<String, String> partitionMap = partitionRDD.collectAsMap(); System. out .println( "partitionMap=" +partitionMap); /** * 两个RDD聚集 * 19、Cogroup算子 * cogroup函数将两个RDD进行协同划分,cogroup函数的定义如下。 cogroup[W](other: RDD[(K, W)], numPartitions: Int): RDD[(K, (Iterable[V], Iterable[W]))] 对在两个RDD中的Key-Value类型的元素,每个RDD相同Key的元素分别聚合为一个集合,并且返回两个RDD中对应Key的元素集合的迭代器。 (K, (Iterable[V], Iterable[W])) 其中,Key和Value,Value是两个RDD下相同Key的两个数据集合的迭代器所构成的元组。 */ List<Tuple2<Integer, String>> DBName= new ArrayList<Tuple2<Integer,String>>(); DBName.add( new Tuple2<Integer, String>(1, "Spark" )); DBName.add( new Tuple2<Integer, String>(2, "Hadoop" )); DBName.add( new Tuple2<Integer, String>(3, "Kylin" )); DBName.add( new Tuple2<Integer, String>(4, "Flink" )); DBName.add( new Tuple2<Integer, String>(6, "Sqoop" )); List<Tuple2<Integer, String>> numType = new ArrayList<Tuple2<Integer,String>>(); numType.add( new Tuple2<Integer, String>(1, "String" )); numType.add( new Tuple2<Integer, String>(2, "int" )); numType.add( new Tuple2<Integer, String>(3, "byte" )); numType.add( new Tuple2<Integer, String>(4, "bollean" )); numType.add( new Tuple2<Integer, String>(5, "float" )); numType.add( new Tuple2<Integer, String>(1, "34" )); numType.add( new Tuple2<Integer, String>(1, "45" )); numType.add( new Tuple2<Integer, String>(2, "47" )); numType.add( new Tuple2<Integer, String>(3, "75" )); numType.add( new Tuple2<Integer, String>(4, "95" )); numType.add( new Tuple2<Integer, String>(5, "16" )); numType.add( new Tuple2<Integer, String>(1, "85" )); JavaPairRDD<Integer, String> DBNameRDD = sc.parallelizePairs(DBName); JavaPairRDD<Integer, String> numTypeRDD = sc.parallelizePairs(numType); JavaPairRDD<Integer, Tuple2<Iterable<String>, Iterable<String>>> coGroupRDD = DBNameRDD.cogroup(numTypeRDD); Map<Integer, Tuple2<Iterable<String>, Iterable<String>>> coGroupMap = coGroupRDD.collectAsMap(); System. out .println( "coGroupMap=" +coGroupMap); /** * 三、连接 20、join算子 21、leftOutJoin和 rightOutJoin算子 */ /** * 20、join算子 * join 对两个需要连接的 RDD 进行 cogroup函数操作,将相同 key 的数据能够放到一个分区, * 在 cogroup 操作之后形成的新 RDD 对每个key 下的元素进行笛卡尔积的操作,返回的结果再展平, * 对应 key 下的所有元组形成一个集合。最后返回 RDD[(K, (V, W))]。 * 下 面 代 码 为 join 的 函 数 实 现, 本 质 是通 过 cogroup 算 子 先 进 行 协 同 划 分, 再 通 过flatMapValues 将合并的数据打散。 * this.cogroup(other,partitioner).f latMapValues{case(vs,ws) => for(v<-vs;w<-ws)yield(v,w) } */ JavaPairRDD<Integer, Tuple2<String, String>> joinRDD = DBNameRDD. join (numTypeRDD); Map<Integer, Tuple2<String, String>> joinMap = joinRDD.collectAsMap(); System. out .println( "joinMap=" +joinMap); /** * 21、leftOutJoin和 rightOutJoin算子 * LeftOutJoin(左外连接)和RightOutJoin(右外连接)相当于在join的基础上先判断一侧的RDD元素是否为空, * 如果为空,则填充为空。 如果不为空,则将数据进行连接运算,并返回结果。 下面代码是leftOutJoin的实现。 if (ws.isEmpty) { vs.map(v => (v, None)) } else { for (v <- vs; w <- ws) yield (v, Some(w)) } */ JavaPairRDD<Integer, Tuple2<String, Optional<String>>> leftOuterJoinRDD = DBNameRDD.leftOuterJoin(numTypeRDD); Map<Integer, Tuple2<String, Optional<String>>> leftOuterJoinMap = leftOuterJoinRDD.collectAsMap(); System. out .println( "leftOuterJoinMap=" +leftOuterJoinMap); JavaPairRDD<Integer, Tuple2<Optional<String>, String>> rightOuterJoinRDD = DBNameRDD.rightOuterJoin(numTypeRDD); Map<Integer, Tuple2<Optional<String>, String>> rightOuterJoinMap = rightOuterJoinRDD.collectAsMap(); System. out .println( "rightOuterJoinMap=" +rightOuterJoinMap); /** * Action算子 一、无输出 22、foreach算子 二、HDFS 23、saveAsTextFile算子 24、saveAsObjectFile算子 三、Scala集合和数据类型 25、collect算子 26、collectAsMap算子 27、reduceByKeyLocally算子 28、lookup算子 29、count算子 30、top算子 31、reduce算子 32、fold算子 33、aggregate算子 */ /** * Action算子 * 本质上在 Action 算子中通过 SparkContext 进行了提交作业的 runJob 操作,触发了RDD DAG 的执行。 例如, Action 算子 collect 函数的代码如下 // Return an array that contains all of the elements in this RDD. def collect(): Array[T] = { //提交 Job val results = sc.runJob(this, (iter: Iterator[T]) => iter.toArray) Array.concat(results: _*) } */ /** * 无输出 * 22、foreach算子 * foreach 对 RDD 中的每个元素都应用 f 函数操作,不返回 RDD 和 Array, 而是返回Uint */ ds. foreach ( new VoidFunction<String>() { @Override public void call(String t) throws Exception { System. out .println(t); } }); /** * 二、HDFS 23、saveAsTextFile算子 24、saveAsObjectFile算子 */ /** * 23、saveAsTextFile算子 * 函数将数据输出,存储到 HDFS 的指定目录。 下面为 saveAsTextFile 函数的内部实现,其内部 通过调用 saveAsHadoopFile 进行实现: this.map(x => (NullWritable.get(), new Text(x.toString))).saveAsHadoopFile[TextOutputFormat[NullWritable, Text]](path) 将 RDD 中的每个元素映射转变为 (null, x.toString),然后再将其写入 HDFS。 */ //firstNameRDD.saveAsTextFile("hdfs://bjqt/data/testSaveAsText"); /** * 24、saveAsObjectFile算子 * saveAsObjectFile将分区中的每10个元素组成一个Array,然后将这个Array序列化, * 映射为(Null,BytesWritable(Y))的元素,写入HDFS为SequenceFile的格式。 下面代码为函数内部实现。 map(x=>(NullWritable.get(),new BytesWritable(Utils.serialize(x)))) */ //firstNameRDD.saveAsObjectFile("hdfs://bjqt/data/testSaveAsObject"); /** * 三、Scala集合和数据类型 25、collect算子 26、collectAsMap算子 27、reduceByKeyLocally算子 28、lookup算子 29、count算子 30、top算子 31、reduce算子 32、fold算子 33、aggregate算子 */ /** * 25、collect算子 * collect 相当于 toArray, toArray 已经过时不推荐使用, collect 将分布式的 RDD 返回为一个单机的 scala Array 数组。 * 在这个数组上运用 scala 的函数式操作。 */ /** * 26、collectAsMap算子 * collectAsMap对(K,V)型的RDD数据返回一个单机HashMap。 对于重复K的RDD元素,后面的元素覆盖前面的元素。 */ /** * 27、reduceByKeyLocally算子 * 实现的是先reduce再collectAsMap的功能,先对RDD的整体进行reduce操作,然后再收集所有结果返回为一个HashMap。 */ Map<String, Integer> reduceByKeyLocallyMap = firstNameCountPairRDD.reduceByKeyLocally( new Function2<Integer, Integer, Integer>() { @Override public Integer call(Integer v1, Integer v2) throws Exception { return v1+v2; } }); System. out .println( "reduceByKeyLocallyMap=" +reduceByKeyLocallyMap); /** * 28、lookup算子 * 下面代码为lookup的声明。 lookup(key:K):Seq[V] Lookup函数对(Key,Value)型的RDD操作,返回指定Key对应的元素形成的Seq。 这个函数处理优化的部分在于,如果这个RDD包含分区器,则只会对应处理K所在的分区, 然后返回由(K,V)形成的Seq。 如果RDD不包含分区器,则需要对全RDD元素进行暴力扫描处理,搜索指定K对应的元素。 */ List<String> lookupList = firstNameRDD.lookup( "441283198412125733" ); System. out .println( "lookupList=" +lookupList); /** * 29、count算子 * count 返回整个 RDD 的元素个数。 内部函数实现为: defcount():Long=sc.runJob(this,Utils.getIteratorSize_).sum */ long count = ds.count(); System. out .println( "count=" +count); /** * 30、top算子 * top可返回最大的k个元素。 函数定义如下。 top(num:Int)(implicit ord:Ordering[T]):Array[T] 相近函数说明如下。 ·top返回最大的k个元素。 ·take返回最小的k个元素。 ·takeOrdered返回最小的k个元素,并且在返回的数组中保持元素的顺序。 ·first相当于top(1)返回整个RDD中的前k个元素,可以定义排序的方式Ordering[T]。 返回的是一个含前k个元素的数组。 */ // List<Tuple2<String, String>> topList = firstNameRDD.top(2, new Comparator<Tuple2<String,String>>() { // @Override // public int compare(Tuple2<String, String> o1, Tuple2<String, String> o2) { // return o1._1.compareTo(o2._1); // } // }); // System.out.println("topList="+topList); List<Tuple2<String, String>> takeList = firstNameRDD.take(2); System. out .println( "takeList=" +takeList); // List<Tuple2<Integer, String>> takeOrderedList = DBNameRDD.takeOrdered(2, new Comparator<Tuple2<Integer,String>>() { // // @Override // public int compare(Tuple2<Integer, String> o1, // Tuple2<Integer, String> o2) { // return o1._1 > o2._1?0:1; // } // }); // System.out.println("takeOrderedList="+takeOrderedList); Tuple2<String, String> first = firstNameRDD.first(); System. out .println( "first=" +first); /** * 31、reduce算子 * reduce函数相当于对RDD中的元素进行reduceLeft函数的操作。 函数实现如下。 Some(iter.reduceLeft(cleanF)) reduceLeft先对两个元素<K,V>进行reduce函数操作,然后将结果和迭代器取出的下一个元素<k,V>进行reduce函数操作, 直到迭代器遍历完所有元素,得到最后结果。在RDD中,先对每个分区中的所有元素<K,V>的集合分别进行reduceLeft。 每个分区形成的结果相当于一个元素<K,V>,再对这个结果集合进行reduceleft操作。 例如:用户自定义函数如下。 f:(A,B)=>(A._1+”@”+B._1,A._2+B._2) */ Tuple2<String, String> reduceTuple2 = firstNameRDD.reduce( new Function2<Tuple2<String,String>, Tuple2<String,String>, Tuple2<String,String>>() { @Override public Tuple2<String, String> call(Tuple2<String, String> v1, Tuple2<String, String> v2) throws Exception { return new Tuple2<String, String>(v1._1+v2._1, v1._2+v2._2); } }); System. out .println( "reduceTuple2=" +reduceTuple2); /** * 32、fold算子 * fold和reduce的原理相同,但是与reduce不同,相当于每个reduce时,迭代器取的第一个元素是zeroValue。 * fold((”V0@”,2))( (A,B)=>(A._1+”@”+B._1,A._2+B._2)) */ Tuple2<String, String> foldTuple2 = firstNameRDD.fold( new Tuple2<String, String>( "" , "" ), new Function2<Tuple2<String,String>, Tuple2<String,String>, Tuple2<String,String>>() { @Override public Tuple2<String, String> call(Tuple2<String, String> v1, Tuple2<String, String> v2) throws Exception { return new Tuple2<String, String>(v1._1+v2._1, v1._2+v2._2); } }); System. out .println( "foldTuple2=" +foldTuple2); /** * 33、aggregate算子 * aggregate先对每个分区的所有元素进行aggregate操作,再对分区的结果进行fold操作。 aggreagate与fold和reduce的不同之处在于,aggregate相当于采用归并的方式进行数据聚集,这种聚集是并行化的。 而在fold和reduce函数的运算过程中,每个分区中需要进行串行处理,每个分区串行计算完结果, 结果再按之前的方式进行聚集,并返回最终聚集结果。 函数的定义如下。 aggregate[B](z: B)(seqop: (B,A) => B,combop: (B,B) => B): B 广播(broadcast)变量:其广泛用于广播Map Side Join中的小表,以及广播大变量等场景。 这些数据集合在单节点内存能够容纳,不需要像RDD那样在节点之间打散存储。 Spark运行时把广播变量数据发到各个节点,并保存下来,后续计算可以复用。 相比Hadoo的distributed cache,广播的内容可以跨作业共享。 Broadcast的底层实现采用了BT机制。 */ String aggregateString = firstNameRDD.aggregate( "" , new Function2<String, Tuple2<String, String>, String>() { @Override public String call(String v1, Tuple2<String, String> v2) throws Exception { System. out .println( "v1=" +v1+ "--v2=" +v2._1+ ">" +v2._2); return v1+ "=" +v2._1; } }, new Function2<String, String, String>() { @Override public String call(String v1, String v2) throws Exception { System. out .println( "<<v1=" +v1+ "--v2=" +v2); return v1+ "-" +v2; } }); System. out .println( "aggregateString=" +aggregateString); sc.close(); } } |
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· 没有源码,如何修改代码逻辑?
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· 记一次.NET内存居高不下排查解决与启示