所有的Action算子底层都是直接或间接调用了runJob方法触发Action的
collect
将数据收集到Driver端,并且收集的时候,是按分区编号的顺序进行收集的,所以sort排序后的数据展示出来才能看出是排好序的,collect有一个问题,就是当需要收集的数据太多时,超过内存空间就不会再收集了,因为collect收集过来的数据是存在内存当中的,不会溢写到磁盘,所以用这种方法展示数据,可能会导致数据丢失
val rdd1 = sc.parallelize(List(1,2,3,4,5), 2)
rdd1.collect
count
统计数据的条数,底层是在每个分内区对该算子传入了一个函数,函数中传入的是一个迭代器,返回的是一个Int类型的数据,使用该迭代器迭代区内的每条数据,每迭代一条就count+1,最后返回迭代出来的区内总条数,上传到Driver端在Driver端调用sum方法,统计所有分区内的数量,得到上一个RDD中的总条数
val rdd1 = sc.parallelize(List(1,2,3,4,5), 2)
var c = rdd1.count
reduce
reduce算子是一个action算子,reduceBy和reduceByKey都是Transformation算子,因为reduce算子是按传入的函数将RDD内所有元素进行聚合成一个,输入的类型和返回的类型都是一样的,它底层也是调用的迭代器,且每个分区对应一个迭代器,在迭代器中调用reduceLeft进行局部reduce,然后每个分区的结果进行全局reduce
val rdd1 = sc.parallelize(List(1,2,3,4,5), 2)
val r = rdd1.reduce(_+_)
aggregate
传入一个初始值和两个函数,第一个函数在分区内聚合,第二个全局聚合,每一个分区会应用一次初始值,然后全局聚合是还会应以一次初始值(和aggregateBy不一样),可以指定局部聚合的函数和全局聚合的函数,可以一样也可以不一样,多个分区的Task是并行执行的,哪一个先计算出结果,不一定,所以全局聚合时应用初始值的时候,不一定应用到哪个task上,有随机性
val func2 = (index: Int, iter: Iterator[String]) => {
iter.map(x => "[partID:" + index + ", value : " + x + "]")
}
val rdd2 = sc.parallelize(List("a","b","c","d","e","f"),2)
rdd2.mapPartitionsWithIndex(func2).collect
rdd2.aggregate("")(_ + _, _ + _)
rdd2.aggregate("=")(_ + _, _ + _)
take
返回一个由数据集的前n个元素组成的数组,按分区顺序取数据,如果第一个分区数据不够,才会去第二个分区内寻找,且shuffle到Driver时,是取一个分区就触发一次action,取了多少个分区的数据就触发多少次action,生成几个Job,这样操作避免将所有数据都发送Driver端再取数据,减少了数据的冗余量.
val rdd1 = sc.parallelize(List(3,2,4,1,5), 2)
var c: Array[Int] = rdd1.take(2)
first
返回数据集中的第一个元素,类似于take(1)(first底层就是调用的take(1)),但take返回的是一个数组,first返回的是一个元素
val rdd1 = sc.parallelize(List(1,2,3,4,5), 2)
var i: Int = rdd1.first
top
将RDD中数据按照降序或者指定的排序规则,返回前n个元素
val rdd1 = sc.parallelize(List(1,2,3,4,5), 2)
var c: Array[Int] = rdd1.top(2)
takeOrdered
takeOrdered和top类似,默认升序返回
val rdd1 = sc.parallelize(List(3,2,4,1,5), 2)
var r: Array[Int] = rdd1.takeOrdered(3)
top和takeOrdered都会按默认的排序规则将RDD里面的元素进行排好序之后再取出前n条数据,只是一个默认升序,一个默认降序,但也可以指定降序还是升序例如:takeOrdered(3)(Ordering.Int.reverse)
将它默认的Int排序规则进行反装,就能实现top(3)的效果
takeOrdered底层是在每个分区中都有一个有界优先队列(BoundedPartitionQueue),该队列会将数据按我们传入的条件进行一条一条的筛选,符合条件的就留下,不符合的就踢掉,队列始终保持固定长度(长度为我们想取出的n条数据),然后每个区拿到满足条件的数据,再调用reduce归并各个分区的数据,取出全局的满足条件的前n个,但队列中不会对数据进行排序,所以拿到数据后底层最后又调用了一次sortBy(非RDD的sortBy),所以takeOrdered排序时不会产生shuffle
使用takeOrdered可以在我们想要对数据排序并取出前n条时使用,但对于一些特殊的数据类型,需要传入我们自定义的排序规则,它才会按要求排序,使用这种方式排序,可以避免一次sortBy排序造成的shuffle,加快运行效率
min和max
取出所有元素中的最大值或最小值,它底层并没有调用排序方法,而是调用的时reduce方法,将第一个元素和第二个元素进行比较,然后取出较大的或较小的,得到中间值,再让中间值和后面的继续比较,从而拿到全局的最大值或最小值
saveAsTextFile
saveAsTextFile以文本的形式保存到文件系统中,底层先调用mapPartititions,在每个分区内将数据转成Hadoop的TextOutputFormat要求的K,V格式(实际上只需要text就可以了,还有一个是nullWritable),然后再调用sc.runJob
val rdd1 = sc.parallelize(List(1,2,3,4,5), 2)
rdd1.saveAsTextFile("hdfs://node-1.51doit.cn:9000/out2")
foreach和foreachPartition / foreachPartitionAsgnc
foreachPartition 同步
foreachPartitionAsgnc 异步
foreachPartition, 和foreach类似,只不过是以分区位单位,一个分区对应一个迭代器,应用外部传的函数,函数没有返回值,通常使用该方法将数据写入到外部存储系统中,一个分区获取一个连接,效果更高
val rdd1 = sc.parallelize(List(1,2,3,4,5,6,7,8,9), 2)
rdd1.foreachPartition(it => it.foreach(println))
需要注意的是:如果我们传入一个打印的逻辑,打印的结果只会出现在Executor端的日志中,因为打印的逻辑是在Executor端执行的,所以不会在Driver端出现打印结果
foreach(x => println(x))