1、aggregate()
方法聲明:
def aggregate[U: ClassTag](zeroValue: U)(seqOp: (U, T) => U, combOp: (U, U) => U): U = withScope {}
方法聲明中的重點:
1、aggregate返回類型爲傳入的類型參數U
2、第一個參數列表zeroValue爲U類型的值,是聚合的初始值。
3、兩個函數類型的參數,官方註釋如下:
* @param seqOp an operator used to accumulate results within a partition
* @param combOp an associative operator used to combine results from different partitions
前兩點不難理解,第三點中我會解釋seqOp ,combOp。
- seqOp傳入參數(U,T),其中U爲累加結果,T爲RDD中下一行的值,返回一個類型爲U的值。可以說第一次調用seqOp時,U的初始值爲第一個參數列表U類型的zeroValue,下一次調用seqOp時,U的值爲上一個SeqOp的返回結果。需要注意的是seqOp只在Partition內執行,最終返回一個U類型的聚合結果,比如第i個partition的聚合結果我們暫且稱爲result_i。
- combOp是用來合併seqOp在不同partition內計算的聚合結果result_i,這也是爲什麼它的傳入參數列表爲(:U,:U),返回也爲U類型。
下面是一個例子,將一個Tuple2類型列表中值轉化爲Json。
(此例爲更鮮明體現aggragete返回的類型更加靈活,不受輸入RDD的限制)
val list2 = List(("a",1),("b",2),("c",3),("d",4))
val rdd6 = sc.parallelize(list2)
var jsonobject = new JSONObject()
val a = rdd6.aggregate[JSONObject](jsonobject)(
(a:JSONObject,b)=>{
a.put(b._1.toString,b._2.toString)
a
}
,(c,d)=>{
val keyset = d.keySet()
import collection.JavaConversions._
for(key<-keyset){
c.put(key,d.getString(key))
}
c
}
)
println(a)
最終結果爲:{“a”:“1”,“b”:“2”,“c”:“3”,“d”:“4”}
2、combineByKey()
combineByKey是PairRDD的算子,pairRdd大部分聚合操作內部都是調用combineByKey()實現的。
先看一下方法的聲明,(有不同參數類型的重載,我們只看參數最少的這一個):
def combineByKeyWithClassTag[C](
createCombiner: V => C,
mergeValue: (C, V) => C,
mergeCombiners: (C, C) => C)(implicit ct: ClassTag[C]): RDD[(K, C)] = self.withScope {
combineByKeyWithClassTag(createCombiner, mergeValue, mergeCombiners, defaultPartitioner(self))
}
三個參數的官方解釋:
* - `createCombiner`, which turns a V into a C (e.g., creates a one-element list)
* - `mergeValue`, to merge a V into a C (e.g., adds it to the end of a list)
* - `mergeCombiners`, to combine two C's into a single one.
- createCombiner:相當於對rdd中的value做一步類似map處理,形成改key下累加初始值,處理成累加器的格式C。
- mergeValue:與aggregate()第一個參數類似,他是將上一個累加器結果和下一行的值做合併處理,將上一個格式爲C累加結果與rdd只能夠的value合併爲一個C類型的新結果。
- mergeCombiners:同aggregate()的第二個參數,合併不同分區中相同key的累加結果。
明顯createCombiner,mergeValue是在同一個partition中進行,而在同一個partition中每次遇到新的key,createCombiner都會重新創建一個初始值。
計算班級平均成績的代碼:
val rdd4 = rdd.map(x=>(x.split(" ")(0),x.split(" ")(2)))
//rdd4:((classA,98),(classA,90),(classB,88))
rdd4.combineByKeyWithClassTag [Tuple2[Int,Int]](
(v:String)=>(v.toInt,1)
,(a:(Int,Int),v:String)=>(a._1+v.toInt,a._2+1)
,(a:(Int,Int),b:(Int,Int))=>(a._1+b._1,b._2+b._2)
).map{case (key,value)=>(key,value._1/value._2)}.foreach(x=>println(x._1+" "+x._2))
3、aggregateByKey()
看完上面兩個這個也就不難理解了。
值得注意的是aggregateByKey()內部是調用了combineByKeyWithClassTag()實現的,,這也印證了“pairRdd大部分聚合操作內部都是調用combineByKey()實現的”說法。
aggregateByKey()部分源碼如下:
def aggregateByKey[U: ClassTag](zeroValue: U, partitioner: Partitioner)(seqOp: (U, V) => U,
combOp: (U, U) => U): RDD[(K, U)] = self.withScope {
// Serialize the zero value to a byte array so that we can get a new clone of it on each key
val zeroBuffer = SparkEnv.get.serializer.newInstance().serialize(zeroValue)
val zeroArray = new Array[Byte](zeroBuffer.limit)
zeroBuffer.get(zeroArray)
lazy val cachedSerializer = SparkEnv.get.serializer.newInstance()
val createZero = () => cachedSerializer.deserialize[U](ByteBuffer.wrap(zeroArray))
// We will clean the combiner closure later in `combineByKey`
val cleanedSeqOp = self.context.clean(seqOp)
combineByKeyWithClassTag[U]((v: V) => cleanedSeqOp(createZero(), v),
cleanedSeqOp, combOp, partitioner)
}
使用樣例:
rdd4.aggregateByKey [Tuple2[Int,Int]]((0,0))(
(a:(Int,Int),v:String)=>(a._1+v.toInt,a._2+1)
,(a:(Int,Int),b:(Int,Int))=>(a._1+b._1,b._2+b._2)
).map{case (key,value)=>(key,value._1/value._2)}.foreach(x=>println(x._1+" "+x._2))