本篇接着讲解RDD的API,讲解那些不是很容易理解的API,同时本篇文章还将展示如何将外部的函数引入到RDD的API里使用,最后通过对RDD的API深入学习,我们还讲讲一些和RDD开发相关的scala语法。

1)  aggregate(zeroValue)(seqOp,combOp)

   该函数的功能和reduce函数一样,也是对数据进行聚合操作,不过aggregate可以返回和原RDD不同的数据类型,使用时候还要提供初始值。

  我们来看看下面的用法,代码如下:

    val rddInt: RDD[Int] = sc.parallelize(List(1, 2, 3, 4, 5), 1)

    val rddAggr1: (Int, Int) = rddInt.aggregate((0, 0))((x, y) => (x._1 + y, x._2 + 1), (x, y) => (x._1 + y._1, x._2 + y._2))
println("====aggregate 1====:" + rddAggr1.toString()) // (15,5)

  该方法是将有数字组成的RDD的数值进行求和,同时还要统计元素的个数,这样我们就可以计算出一个平均值,这点在实际运算中是非常有用的。

  假如读者不太懂scala语言,或者就算懂那么一点点scala语法,该API的使用还是让人很难理解的,这个x是什么东西,这个y又是什么东西,为什么它们放在一起这么运算就可以得到预期结果呢?

  其实aggregate方法使用了scala里元组的结构,元组是scala里很具特色的数据结构,我们看看下面的代码:

    val tuple2Param1:Tuple2[String,Int] = Tuple2("x01",12)// 标准定义二元组
val tuple2Param2:(String,Int) = ("x02",29)// 字面量定义二元组 /* 结果: x01:12*/
println("====tuple2Param1====:" + tuple2Param1._1 + ":" + tuple2Param1._2)
/* 结果: x02:29 */
println("====tuple2Param2====:" + tuple2Param2._1 + ":" + tuple2Param2._2) val tuple6Param1:Tuple6[String,Int,Int,Int,Int,String] = Tuple6("xx01",1,2,3,4,"x1x")// 标准定义6元组
val tuple6Param2:(String,Int,Int,Int,Int,String) = ("xx02",1,2,3,4,"x2x")// 字面量定义6元组 /* 结果: xx01:1:2:3:4:x1x */
println("====tuple6Param1====:" + tuple6Param1._1 + ":" + tuple6Param1._2 + ":" + tuple6Param1._3 + ":" + tuple6Param1._4 + ":" + tuple6Param1._5 + ":" + tuple6Param1._6)
/* 结果: xx02:1:2:3:4:x2x */
println("====tuple6Param2====:" + tuple6Param2._1 + ":" + tuple6Param2._2 + ":" + tuple6Param2._3 + ":" + tuple6Param2._4 + ":" + tuple6Param2._5 + ":" + tuple6Param2._6)

  元组在scala里使用Tuple来构造,不过实际运用中我们会给Tuple带上数字后缀,例如Tuple2就是二元组它包含两个元素,Tuple6是6元组它包含6个元素,元组看起来很像数组,但是数组只能存储相同数据类型的数据结构,而元组是可以存储不同数据类型的数据结构,元组里元素访问使用_1,_2这样的形式,第一个元素是从1开始标记的,这点和数组是不同的。实际使用中我们很少使用Tuple构造元组,而是使用字面量定义方式(参见代码注释),由此我们可以看出spark里键值对RDD其实就是使用二元组来表示键值对数据结构,回到aggregate方法,它的运算也是通过二元组这种数据结构完成的。

  下面我们来看看aggregate的运算过程,这里我将aggregate方法里的算子都使用外部函数,代码如下所示:

  def aggrFtnOne(par: ((Int, Int), Int)): (Int, Int) = {
/*
*aggregate的初始值为(0,0):
====aggrFtnOne Param===:((0,0),1)
====aggrFtnOne Param===:((1,1),2)
====aggrFtnOne Param===:((3,2),3)
====aggrFtnOne Param===:((6,3),4)
====aggrFtnOne Param===:((10,4),5)*/
/*
*aggregate的初始值为(1,1):
====aggrFtnOne Param===:((1,1),1)
====aggrFtnOne Param===:((2,2),2)
====aggrFtnOne Param===:((4,3),3)
====aggrFtnOne Param===:((7,4),4)
====aggrFtnOne Param===:((11,5),5)
* */
println("====aggrFtnOne Param===:" + par.toString())
val ret: (Int, Int) = (par._1._1 + par._2, par._1._2 + 1)
ret
} def aggrFtnTwo(par: ((Int, Int), (Int, Int))): (Int, Int) = {
/*aggregate的初始值为(0,0):::::((0,0),(15,5))*/
/*aggregate的初始值为(1,1):::::((1,1),(16,6))*/
println("====aggrFtnTwo Param===:" + par.toString())
val ret: (Int, Int) = (par._1._1 + par._2._1, par._1._2 + par._2._2)
ret
} val rddAggr2: (Int, Int) = rddInt.aggregate((0, 0))((x, y) => aggrFtnOne(x, y), (x, y) => aggrFtnTwo(x, y)) // 参数可以省略元组的括号
println("====aggregate 2====:" + rddAggr2.toString()) // (15,5) val rddAggr3: (Int, Int) = rddInt.aggregate((1, 1))((x, y) => aggrFtnOne((x, y)), (x, y) => aggrFtnTwo((x, y))) // 参数使用元组的括号
println("====aggregate 3====:" + rddAggr3.toString()) // (17,7)

  由以上代码我们就可以清晰看到aggregate方法的实际运算过程了。

  aggrFtnOne方法的参数格式是((Int, Int), Int),这个复合二元组里第二个元素才是实际的值,而第一个元素就是我们给出的初始化值,第一个元素里的第一个值就是我们实际求和的值,里面第二个元素就是累计记录元素个数的值。

  aggrFtnTwo方法的参数里的二元组第一个元素还是初始化值,第二个元素则是aggrFtnOne计算的结果,这样我们就可以计算出我们要的结果。

  作为对比我将初始化参数改为(1,1)二元组,最终结果在求和运算以及计算元素个数上都会加2,这是因为初始化值两次参入求和所致的,由上面代码我们可以很清晰的看到原因所在。

  如果我们想要结果二元组里第一个元素求积那么初始化值就不能是(0,0),而应该是(1,0),理解了原理我们就很清晰知道初始值该如何设定了,具体代码如下:

    val rddAggr4: (Int, Int) = rddInt.aggregate((1, 0))((x, y) => (x._1 * y, x._2 + 1), (x, y) => (x._1 * y._1, x._2 + y._2))
println("====aggregate 4====:" + rddAggr4.toString()) // (120,5)

  

2) fold(zero)(func)

 该函数和reduce函数功能一样,只不过使用时候需要加上初始化值。

 代码如下所示:

  def foldFtn(par: (Int, Int)): Int = {
/*fold初始值为0:
=====foldFtn Param====:(0,1)
=====foldFtn Param====:(1,2)
=====foldFtn Param====:(3,3)
=====foldFtn Param====:(6,4)
=====foldFtn Param====:(10,5)
=====foldFtn Param====:(0,15)
* */
/*
* fold初始值为1:
=====foldFtn Param====:(1,1)
=====foldFtn Param====:(2,2)
=====foldFtn Param====:(4,3)
=====foldFtn Param====:(7,4)
=====foldFtn Param====:(11,5)
=====foldFtn Param====:(1,16)
* */
println("=====foldFtn Param====:" + par.toString())
val ret: Int = par._1 + par._2
ret
} val rddFold2: Int = rddInt.fold(0)((x, y) => foldFtn(x, y)) // 参数可以省略元组的括号
println("====fold 2=====:" + rddFold2) // 15 val rddFold3: Int = rddInt.fold(1)((x, y) => foldFtn((x, y))) // 参数使用元组的括号
println("====fold 3====:" + rddFold3) // 17

  我们发现当初始化值为1时候,求和增加的不是1而是2,原因就是fold计算时候为了凑齐一个完整的二元组,在第一个元素计算以及最后一个元素计算时候都会让初始化值凑数组成二元组,因此初始值会被使用两遍求和,因此实际结果就不是增加1,而是增加2了。

  作为对比我们看看reduce实际运算过程,代码如下:

  def reduceFtn(par:(Int,Int)):Int = {
/*
* ======reduceFtn Param=====:1:2
======reduceFtn Param=====:3:3
======reduceFtn Param=====:6:4
======reduceFtn Param=====:10:5
*/
println("======reduceFtn Param=====:" + par._1 + ":" + par._2)
par._1 + par._2
} val rddReduce1:Int = rddInt.reduce((x,y) => x + y)
println("====rddReduce 1====:" + rddReduce1)// 15 val rddReduce2:Int = rddInt.reduce((x,y) => reduceFtn(x,y))
println("====rddReduce 2====:" + rddReduce2)// 15

  

3) combineByKey[C](createCombiner: Int => C, mergeValue: (C, Int) => C, mergeCombiners: (C, C) => C): RDD[(String, C)]

  combineByKey作用是使用不同的返回类型合并具有相同键的值,combineByKey适用键值对RDD,普通RDD是没有这个方法。

  有上面定义我们看到combineByKey会经过三轮运算,前一个运算步骤结果就是下一个运算步骤的参数,我们看下面的代码:

  def combineFtnOne(par:Int):(Int,Int) = {
/*
* ====combineFtnOne Param====:2
====combineFtnOne Param====:5
====combineFtnOne Param====:8
====combineFtnOne Param====:3
*/
println("====combineFtnOne Param====:" + par)
val ret:(Int,Int) = (par,1)
ret
} def combineFtnTwo(par:((Int,Int),Int)):(Int,Int) = {
/*
====combineFtnTwo Param====:((2,1),12)
====combineFtnTwo Param====:((8,1),9)
* */
println("====combineFtnTwo Param====:" + par.toString())
val ret:(Int,Int) = (par._1._1 + par._2,par._1._2 + 1)
ret
} def combineFtnThree(par:((Int,Int),(Int,Int))):(Int,Int) = {
/*
* 无结果打印
*/
println("@@@@@@@@@@@@@@@@@@")
println("====combineFtnThree Param===:" + par.toString())
val ret:(Int,Int) = (par._1._1 + par._2._1,par._1._2 + par._2._2)
ret
} val rddPair: RDD[(String, Int)] = sc.parallelize(List(("x01", 2), ("x02", 5), ("x03", 8), ("x04", 3), ("x01", 12), ("x03", 9)), 1) /* def combineByKey[C](createCombiner: Int => C, mergeValue: (C, Int) => C, mergeCombiners: (C, C) => C): RDD[(String, C)] */
val rddCombine1:RDD[(String,(Int,Int))] = rddPair.combineByKey(x => (x, 1), (com: (Int, Int), x) => (com._1 + x, com._2 + 1), (com1: (Int, Int), com2: (Int, Int)) => (com1._1 + com2._1, com1._2 + com2._2))
println("====combineByKey 1====:" + rddCombine1.collect().mkString(",")) // (x02,(5,1)),(x03,(17,2)),(x01,(14,2)),(x04,(3,1)) val rddCombine2:RDD[(String,(Int,Int))] = rddPair.combineByKey(x => combineFtnOne(x), (com: (Int, Int), x) => combineFtnTwo(com,x), (com1: (Int, Int), com2: (Int, Int)) => combineFtnThree(com1,com2))
println("=====combineByKey 2====:" + rddCombine2.collect().mkString(",")) // (x02,(5,1)),(x03,(17,2)),(x01,(14,2)),(x04,(3,1))

  这个算法和上面aggregate求和方法很像,不过combineByKey很奇怪,它第三个算子似乎并没有被执行,第二个算子打印的信息也不齐备,不过我认为它们都执行了,只不过有些语句没有打印出来,至于原因为何,我以后再研究下吧。

  本篇就写到这里吧,其余内容我在下篇里讲解了。

Spark笔记:复杂RDD的API的理解(上)的更多相关文章

  1. Spark笔记:RDD基本操作(下)

    上一篇里我提到可以把RDD当作一个数组,这样我们在学习spark的API时候很多问题就能很好理解了.上篇文章里的API也都是基于RDD是数组的数据模型而进行操作的. Spark是一个计算框架,是对ma ...

  2. Spark笔记:RDD基本操作(上)

    本文主要是讲解spark里RDD的基础操作.RDD是spark特有的数据模型,谈到RDD就会提到什么弹性分布式数据集,什么有向无环图,本文暂时不去展开这些高深概念,在阅读本文时候,大家可以就把RDD当 ...

  3. spark 笔记 6: RDD

    了解RDD之前,必读UCB的论文,个人认为这是最好的资料,没有之一. http://www.cs.berkeley.edu/~matei/papers/2012/nsdi_spark.pdf A Re ...

  4. Spark笔记:复杂RDD的API的理解(下)

    本篇接着谈谈那些稍微复杂的API. 1)   flatMapValues:针对Pair RDD中的每个值应用一个返回迭代器的函数,然后对返回的每个元素都生成一个对应原键的键值对记录 这个方法我最开始接 ...

  5. spark 中的RDD编程 -以下基于Java api

    1.RDD介绍:     RDD,弹性分布式数据集,即分布式的元素集合.在spark中,对所有数据的操作不外乎是创建RDD.转化已有的RDD以及调用RDD操作进行求值.在这一切的背后,Spark会自动 ...

  6. Spark学习笔记3——RDD(下)

    目录 Spark学习笔记3--RDD(下) 向Spark传递函数 通过匿名内部类 通过具名类传递 通过带参数的 Java 函数类传递 通过 lambda 表达式传递(仅限于 Java 8 及以上) 常 ...

  7. Spark学习笔记2——RDD(上)

    目录 Spark学习笔记2--RDD(上) RDD是什么? 例子 创建 RDD 并行化方式 读取外部数据集方式 RDD 操作 转化操作 行动操作 惰性求值 Spark学习笔记2--RDD(上) 笔记摘 ...

  8. Spark学习之RDD的理解

    转自:http://www.infoq.com/cn/articles/spark-core-rdd/ 感谢张逸老师的无私分享 RDD,全称为Resilient Distributed Dataset ...

  9. Spark学习笔记之RDD中的Transformation和Action函数

    总算可以开始写第一篇技术博客了,就从学习Spark开始吧.之前阅读了很多关于Spark的文章,对Spark的工作机制及编程模型有了一定了解,下面把Spark中对RDD的常用操作函数做一下总结,以pys ...

随机推荐

  1. wepack+sass+vue 入门教程(二)

    六.新建webpack配置文件 webpack.config.js 文件整体框架内容如下,后续会详细说明每个配置项的配置 webpack.config.js直接放在项目demo目录下 module.e ...

  2. EntityFramework Core Raw SQL

    前言 本节我们来讲讲EF Core中的原始查询,目前在项目中对于简单的查询直接通过EF就可以解决,但是涉及到多表查询时为了一步到位就采用了原始查询的方式进行.下面我们一起来看看. EntityFram ...

  3. Chrome出了个小bug:论如何在Chrome下劫持原生只读对象

    Chrome出了个小bug:论如何在Chrome下劫持原生只读对象 概述 众所周知,虽然JavaScript是个很灵活的语言,浏览器里很多原生的方法都可以随意覆盖或者重写,比如alert.但是为了保证 ...

  4. 使用 .NET WinForm 开发所见即所得的 IDE 开发环境,实现不写代码直接生成应用程序

    直接切入正题,这是我09年到11年左右业余时间编写的项目,最初的想法很简单,做一个能拖拖拽拽就直接生成应用程序的工具,不用写代码,把能想到的业务操作全部封装起来,通过配置的方式把这些业务操作组织起来运 ...

  5. .NET 基础 一步步 一幕幕[面向对象之方法、方法的重载、方法的重写、方法的递归]

    方法.方法的重载.方法的重写.方法的递归 方法: 将一堆代码进行重用的一种机制. 语法: [访问修饰符] 返回类型 <方法名>(参数列表){ 方法主体: } 返回值类型:如果不需要写返回值 ...

  6. 15个关于Chrome的开发必备小技巧[译]

    谷歌Chrome,是当前最流行且被众多web开发人员使用的浏览器.最快六周就更新发布一次以及伴随着它不断强大的开发组件,使得Chrome成为你必备的开发工具.例如,在线编辑CSS,console以及d ...

  7. 获取微软原版“Windows 10 推送器(GWX)” 卸载工具

    背景: 随着Windows 10 免费更新的结束,针对之前提供推送通知的工具(以下简称GWX)来说使命已经结束,假设您还未将Windows 8.1 和Windows 7 更新到Windows 10 的 ...

  8. spring boot 部署为jar

    前言 一直在ide中敲代码,使用命令行mvn spring-boot:run或者gradlew bootRun来运行spring boot项目.想来放到prod上面也应该很简单.然而今天试了下,各种问 ...

  9. Entity Framework的启动速度优化

    最近开发的服务放到IIS上寄宿之后,遇到一些现象,比如刚部署之后,第一次启动很慢:程序放置一会儿,再次请求也会比较慢.比如第一个问题,可以解释为初次请求某一个服务的时候,需要把程序集加载到内存中可能比 ...

  10. Maven多模块,Dubbo分布式服务框架,SpringMVC,前后端分离项目,基础搭建,搭建过程出现的问题

    现互联网公司后端架构常用到Spring+SpringMVC+MyBatis,通过Maven来构建.通过学习,我已经掌握了基本的搭建过程,写下基础文章为而后的深入学习奠定基础. 首先说一下这篇文章的主要 ...