方法的定义:
def combineByKey[C](
createCombiner: V => C,
mergeValue: (C, V) => C,
mergeCombiners: (C, C) => C): RDD[(K, C)]
1、作用:对相同 key,把valueList合并成一个值。比较像reduceByKey,但还是有区别的。
2、原理
每个分区的相同key有多个value,其中每个key的第一个value会使用createCombiner创建一个新的值newValue。
然后相同key的除第一个value之外的其他value都与newValue使用mergeValue再合并为一个newValue。
最终同一个分区,每个key的多个value会合并为一个newValue。
其他代码类比:
可以用其他代码来自己封装一个CombineByKey。。下面只是根据原理写的。。方便理解,但真实的不是这样实现的。
implicit class Custom[K:ClassTag, V:ClassTag](rdd: RDD[(K, V)]) {
def myCombineByKey[newV](createCombiner: V => newV, mergeValue: (newV, V) => newV, mergeCombiners: (newV, newV) => newV)(implicit kt: ClassTag[newV]): RDD[(K, newV)] = {
//每个分区的相同key有多个value,其中每个key的第一个value会使用createCombiner创建一个新的值newValue。
//然后相同key的除第一个value之外的其他value都与newValue使用mergeValue再合并为一个newValue。
//最终同一个分区,每个key的多个value会合并为一个newValue。
val partitionMergeRDD: RDD[(K, newV)] = rdd.mapPartitions(partIterator => {
//key和累加器的映射
val keyAndAccMap: mutable.Map[K, newV] = mutable.Map()
//遍历该分区的所有元素,每个元素都是k-v类型
partIterator.foreach(tuple => {
val key: K = tuple._1
val value: V = tuple._2
//如果是第一次碰到该key的数据,那么就使用value通过createCombiner创建一个新的newValue作为累加器初始值
if (!keyAndAccMap.contains(key)) {
val initValue: newV = createCombiner(value)
keyAndAccMap(key) = initValue
} else {
//旧累加器值
val oldAccValue: newV = keyAndAccMap(key)
//新累加器值=旧累加器值+当前k-v的value值
val newAccValue: newV = mergeValue(oldAccValue, value)
keyAndAccMap(key) = newAccValue
}
})
//此时当前分区内相同的key的所有value都最终合并为一个newValue
val iterator: Iterator[(K, newV)] = keyAndAccMap.iterator
iterator
})
//分区之间的数据,相同key的values使用mergeCombiners进行合并
partitionMergeRDD.reduceByKey(mergeCombiners)
}
}
总结:reduceByKey底层就是用CombineByKey实现的。