(4)二元组操作(key-value键值对操作)
开始:创建Pair RDD。就是(key,value)这样的二元组。
(以键值对集合[(1,2),(3,4),(3,6)]为例)
reduceByKey():合并具有相同键的值。传入一个有两个形参的函数,处理过程:进入一条数据,根据key值hash()到一个分区内,分区内如果有其它或之前合并后的元素,调用函数处理两个元素的value值(两个形参的由来),两个元素合并为一个元素,为(key,函数return出来的数),如果hash()后没有自然不做。以上面为例,reduceByKey(lambda x,y:x+y),结果为:[(1,2),(3,10)]。分区:提前分区可以减少hash()操作,操作后会保留之前的分区(后面很多操作提前分区,操作后会保留之前分区,如没分区,后会默认hash()分区),操作后已经是分区状态(hash()处理过)。
groupByKey():对具有相同键的值进行分组。基础不需要传参。以上面为例,结果为:[(1,2),(3,[4,6])]。分区:保留分区方式,没分区,处理和处理后默认保留hash()分区。
mapValue():对pair RDD中的每个值应用一个函数而不改变键。传入一个函数。如:mapValue(lambda x:x+1),结果:[(1,3),(3,5),(3,6)]。分区:这个函数是为了保留分区方式,如果只修改value,不修改key,使用map()不会保留分区方式,因为分区是按key值来的,而理论上map()可以同时改变key的值,所以map()不保留分区。而mapValue()不涉及分区,所以只保留分区,而不默认设置分区。
flatMapValue():对pair RDD中的每个值应用一个返回迭代器的函数,然后对返回的每个元素都生成一个对应原键的键值对记录。传入一个函数。如:flatMapValue(lambda x:[x,1]),结果:[(1,2),(1,1),(3,4),(3,1),(3,6),(3,1)]。分区:与mapValue()基本相同。
combineByKey():这个是mapValue()+reduceByKey()的组合 ,传入三个函数,第一个是组合器,第二个是分区内计算函数,第三个是分区间计算函数。如:combineByKey((lambda x:(x,1)),(lambda x,y:(x[0]+y,x[1]+1)),(lambda x,y:(x[0]+y[0],x[1]+y[1]))).结果是: [(1,(2,1)),(3,(10,2))]。这个方法很像之前对单个value处理的组合方法aggregate(),不过这个是给key-value做的。分区:保留分区,未做分区,计算后默认hash()分区,因为需要筛选key值,但不改变key值。
sortByKey():自定义排序方法。如:sortByKey(ascending=True,numPartitions=None,keyfunc=lambda x:str(x))。里面有三个参数。ascending为一个bool型,表示我们是否想要让结果按升序排列(默认值为true为升序)。numPartions书上没写,但根据测试,给个2会报value值没有的错误,和上面一样,但给1可以出元素,估计这是一个给分区个数的参数,因为对key值排序需要筛选,但因为单机,so,一般是减少分区,减少数据跨区的消耗。keyfunc,给一个方法,根据return出的进行排序。分区:会为结果rdd设好分区方式。书上没给是否保留之前分区方式相关的提示,也无从考证,但估计是保留之前分区,因为分区后估计已经知道大小了,直接排序就好了。
keys():返回一个仅包含键的RDD。如:[1,3,3]。
values():返回一个仅包含值的RDD。如:[2,4,6]。
(5)针对两个pair RDD的转化操作
rdd=[(1,2),(3,4),(3,6)]。other=[(3,9)]
这个小节中的分区: 如果rdd有分区,结果为rdd的分区方式。如果rdd没有,other有,用other的分区方式。如果都没有,结果为默认hash()分区。如果都有,结果为rdd的分区方式。提前分区可以减少方法使用时的消耗。我的理解:没分区,筛选时,把rdd和other中相同key的元素hash到一个分区,数据流有两个,rdd到分区和other到分区。而rdd或other其中一个已经分区了,相当于其中一个已经hash过了,这个数据流已经做了,使用方法时只需要另一个hash就行了。提前hash的作用是,如果一个rdd被频繁的做join等操作时,如果不提前hash和persist()保持在内存中,每一次的操作都需要hash和计算一遍,因为spark是先找到计算方式,等到出现行动操作时,才进行实际运算,行动操作完成后,rdd就不存在内存中了,想要用,需要从开始一步一步重新计算和hash,是不是很麻烦,如果我没看错书的话,书上确实是这么写的,单机数据量太小看不出来。
subtractByKey():删掉RDD中键与other RDD中的键相同的元素。如:subtractByKey(other),结果为:[(1,2)]。这个是key-value中的减法。
join():对两个RDD进行内连接。如:join(other),结果为:[(3,(4,9)),(3,(6,9))]。过程是:rdd中的元素逐条和other中的元素判断,如果key相同,返回一个(相同key,(rdd-value,other-value))。很像在相同key分区中进行笛卡儿积。
rightOuterJoin():对两个RDD进行连接操作,确保第一个RDD的键必须存在(右外连接)。结果和上面join一样,也很像在相同key分区中进行笛卡儿积。为什么一样呢?有什么区别呢?看解释,“确保第一个RDD的键必须存在”,如果“不存在”呢?那不好意思,返回没有“你”(第一个RDD)的东西,如:rdd=[(1,2),(6,8)],other=[(3,9),(3,1)],rdd.rightOuterJoin(other)。结果是:[(3,(None,9)),(3,(None,1))]。个人对待“外”这个字如果理解不一样,有歧义的话,怎么理解?感觉为这个函数命名的非常好客,宁愿自己不存在,也要客人好。
leftOuterJoin():对两个RDD进行连接操作,确保第二个RDD的键必须存在(左外连接)。结果是: [(1,(2,None)),(3,(4,9)),(3,(6,9))](这样好看点)。和上面一样,这次左边的是客人(外),结果刚好相反[(6,(8,None)),(1,(2,None))]。遇到好客的命名者真的让人很难理解呢。
cogroup():将两个RDD中拥有相同键的数据分组到一起。rdd.cogroup(other)。结果是:[(1,([2],[])),(3,([4,6],[9]))]。这个是书上的结果,真正运行得到的,结果中value部分(就是那个元组中的元组),以两个迭代器方式返回。如图:
(6)分区
从分区中获益的操作:cogroup(),groupWith(),join(),leftOuterJoin(),rightOuterJoin(),groupByKey(),reduceByKey(),combineByKey()以及lookup()。
groupWith():是cogroup()的升级版,解决了cogroup()只可以两个rdd之间的操作,现在这个我试3个没毛病,当然结果value中是3个以“,”隔开的三个迭代器,如图:
lookup():返回给定键对应的所有value。如:rdd.lookup(3);,[(1,2),(3,4),(3,6)],结果是:[4,6]。
所有会为生成的结果RDD设好分区方式的操作:cogroup(),groupWith(),join(),leftOuterJoin(),rightOuterJoin(),groupByKey(),reduceByKey(),combineByKey(),partitionBy()(分区函数,设置分区的个数),sort()(不知道和sortByKey()有什么区别,书上也没讲,不过排序不需要知道谁大谁小么?分区很正常), mapValue(), flatMapValues(), 以及filter()(最后三个,如果父RDD有分区方式的话会保留分区方式)。