• Spark常用算子-KeyValue数据类型的算子


    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();
    	}
    }
    

      

  • 相关阅读:
    VUE-cli使用
    2017/04/09王晨分享课大纲
    CommonJS模块和ES6模块的区别
    css常见布局方式
    从输入 URL 到页面加载完成的过程中都发生了什么
    函数节流与防抖的实现
    JavaScript表单
    jQuery方法实现
    移动端去除横向滚动条
    请假时碰到法定假期,实际请假几天?
  • 原文地址:https://www.cnblogs.com/EnzoDin/p/9254576.html
Copyright © 2020-2023  润新知