目录
1. 将数据加载到内存中去TopN
将数据加载到内存和本地中去取TopN
使用自定义分区器取TopN
保存中间结果数据取TopN
使用TreeSet取TopN
使用TreeSet取TopN方式二
在mr、hive中投处理过的操作,分组的topn
比如要从10个文件,每个文件都有100w个数字,找出最大的10数字。
比如有很多部分,比如研发部、设计部、市场部、行政部等等,要求找出每个部分年龄最小的三个小姐姐。
这就是分组TopN的问题。
object _03SparkGroupTopNOps {
def main(args: Array[String]): Unit = {
val conf = new SparkConf()
.setAppName(s"${_01SparkSortOps.getClass.getSimpleName}")
.setMaster(“local[2]”)
val sc = new SparkContext(conf)
val lines = sc.textFile("file:///E:/data/spark/topn.txt")
val course2Info:RDD[(String, String)] = lines.map(line => {
val fields = line.split("\\s+")
val course = fields(0)
val name = fields(1)
val score = fields(2)
(course, s"$name|$score")
})
//就需要将每门课程的所有信息弄到一起才能排序
val course2Infos:RDD[(String, Iterable[String])] = course2Info.groupByKey()
/*
排序
k,是科目
v:该科目对应的所有的成绩信息
经过排序之后返回三个人的成绩信息,还是一个集合
[k, Iterable[String]] --> [k, Iterable[String]]只不过后面Iterable[String]的size为3
还是one-2-many的操作
one-2-one--->map
*/
val top3:RDD[(String, mutable.TreeSet[String])] = course2Infos.map{case (course, infos) => {
var top3Infos = mutable.TreeSet[String]()(new Ordering[String](){
//name|score
override def compare(x: String, y: String) = {
val xScore = x.substring(x.indexOf("|") + 1).toInt
val yScore = y.substring(y.indexOf("|") + 1).toInt
var ret = xScore.compareTo(yScore)
if(ret == 0) {
1
} else {
ret
}
}
})
//排序的操作 top3Infos是有序的,但是最后只要3个
// top3Infos.dropRight(top3Infos.size - 3)
for(info <- infos) {
top3Infos.add(info)
if(top3Infos.size > 2) {
top3Infos = top3Infos.dropRight(1)
}
}
(course, top3Infos)
}}
import org.apache.spark.rdd.RDD
import org.apache.spark.{SparkConf, SparkContext}
/**
* 将数据加载到内存中进项排序取topn
*/
object Teacher01 {
def main(args: Array[String]): Unit = {
// 创建配置参数对象,进行参数配置
val conf = new SparkConf().setAppName(this.getClass.getSimpleName)
var isLocal:Boolean = args(0).toBoolean
if(isLocal){
conf.setMaster("local[*]")
}
// 创建Spark入口
val sc = new SparkContext(conf)
// 读取文件
val lines: RDD[String] = sc.textFile(args(1))
// 对数据进行切分聚合处理
val subTeacherAndOne: RDD[((String, String), Int)] = lines.map(line => {
val strArr = line.split("/")
val url: String = strArr(2)
val teacher: String = strArr(3)
val subject = url.substring(0, url.indexOf("."))
((subject, teacher), 1)
}).reduceByKey(_ + _)
// 因为SortBy是全局进行排序,所以不能直接使用,先要进行进行分组,在进行组内排序
val grouped: RDD[(String, Iterable[((String, String), Int)])] = subTeacherAndOne.groupBy(_._1._1)
// 组内进行排序
val res: RDD[(String, List[(String, Int)])] = grouped.mapValues(it => {
// 将数据加载到内存中
it.toList.sortBy(-_._2).take(2).map(t => (t._1._2, t._2))
})
// 将结果收集到客户端,打印
val buffer = res.collect().toBuffer
println(buffer)
// 释放资源
sc.stop()
}
}
import org.apache.spark.rdd.RDD
import org.apache.spark.{SparkConf, SparkContext}
/**
* 代码优化,如果将数据全部加载到内存中进行处理,如果数据量过大,可能会导致
* 内存溢出,数据丢失,使用RDD的sortBy方法进行排序可以将数据加载到内存和
* 磁盘中
*/
object Teacher02 {
def main(args: Array[String]): Unit = {
// 创建配置参数对象,进行参数配置
val conf = new SparkConf().setAppName(this.getClass.getSimpleName)
var isLocal:Boolean = args(0).toBoolean
if(isLocal){
conf.setMaster("local[*]")
}
// 创建Spark入口
val sc = new SparkContext(conf)
// 读取文件
val lines: RDD[String] = sc.textFile(args(1))
// 对数据进行切分聚合处理
val subTeacherAndOne: RDD[((String, String), Int)] = lines.map(line => {
val strArr = line.split("/")
val url: String = strArr(2)
val teacher: String = strArr(3)
val subject = url.substring(0, url.indexOf("."))
((subject, teacher), 1)
}).reduceByKey(_ + _)
// 创建学科数组用于分组排序
val subjects = Array("bigdata", "javaee", "php")
// 对应数组中的学科学科进行过滤排序(等同于分组排序)
for (sub <- subjects){
// 按照学科将数据进行过滤出来
val filtered: RDD[((String, String), Int)] = subTeacherAndOne.filter(t => t._1._1.equals(sub))
// RDD的takeOrdered方法,可以设置排序规则,并指定取前几个值
val res: Array[((String, String), Int)] = filtered.takeOrdered(2)((a, b) => a._2 - b._2)
// 打印结果
println(res.toBuffer)
}
sc.stop()
}
}
import org.apache.spark.{Partitioner, SparkConf, SparkContext}
import org.apache.spark.rdd.RDD
import scala.collection.mutable
/**
* 使用自定义分区器取topn,可以将较大的数据按照分区加载到内存中进行排序取topn,这样减小了
* 内存的压力,同时也加快了数据,但是如果某个分区中的数据量过大的话,这个方法就不适用了
*/
object Teacher03 {
def main(args: Array[String]): Unit = {
// 创建配置参数对象,进行参数配置
val conf = new SparkConf().setAppName(this.getClass.getSimpleName)
var isLocal:Boolean = args(0).toBoolean
if(isLocal){
conf.setMaster("local[*]")
}
// 创建Spark入口
val sc = new SparkContext(conf)
// 读取文件
val lines: RDD[String] = sc.textFile(args(1))
// 对数据进行切分聚合处理
val subTeacherAndOne: RDD[((String, String), Int)] = lines.map(line => {
val strArr = line.split("/")
val url: String = strArr(2)
val teacher: String = strArr(3)
val subject = url.substring(0, url.indexOf("."))
((subject, teacher), 1)
}).reduceByKey(_ + _)
// 将学科信息收集到客户端
val subjects: Array[String] = subTeacherAndOne.map(_._1._1).distinct().collect()
// 根据自定义的分区器进行分区,有几个学科就有几个分区
val partitioned: RDD[((String, String), Int)] = subTeacherAndOne.partitionBy(new SubjectPartitioner(subjects))
// 使用mapPartition进行区内排序取topn
val res: RDD[((String, String), Int)] = partitioned.mapPartitions(t => {
val it = t.toList.sortBy(_._2).take(2).iterator
it
})
// 打印结果
println(res.collect().toBuffer)
// 释放资源
sc.stop()
}
}
/**
* 自定义分区器在创建好对象后,task会去执行里面的方法,getPartition(key: Any)这个方法
* 中的key,task会根据自己设定的key.asInstanceOf的类型,进行匹配对应的类型
* @param subjects
*/
class SubjectPartitioner(subjects : Array[String]) extends Partitioner {
// 设置分区规则,按照学科指定分区
private val ruels = new mutable.HashMap[String, Int]()
var i:Int = 0
// 将指定每个学科归属哪个分区
for (sub <- subjects){
ruels(sub) = i
i += 1
}
// 重写指定分区数量的方法
override def numPartitions: Int = subjects.length
// 重写获取分区的方法
override def getPartition(key: Any): Int = {
val subject: String = key.asInstanceOf[(String, String)]._1
ruels(subject)
}
}
import org.apache.spark.rdd.RDD
import org.apache.spark.{Partitioner, SparkConf, SparkContext}
import scala.collection.mutable
/**
*使用自定义分区器取topn,上一种写法存在着不足,因为如果中间结果数据比较珍贵,并且是经过了多天的计算得出的结果
*那么在计算过程中,如果计算机出了问题,那就会导致多天的计算毁于一旦,所以可以将中间结果数据保存到hfds中,这样
*可以增加数据的安全性
*/
object TeacherTopN4 {
def main(args: Array[String]): Unit = {
val isLocal = args(0).toBoolean
val conf = new SparkConf().setAppName(this.getClass.getSimpleName)
if(isLocal) {
conf.setMaster("local[*]")
}
val sc = new SparkContext(conf)
//指定以后从哪里读取数据创建RDD
val lines = sc.textFile(args(1))
//对数据进行切分整理
val subjectTeacherAndOne: RDD[((String, String), Int)] = lines.map(line => {
val fields = line.split("/")
val url = fields(2)
val teacher = fields(3)
val subject = url.substring(0, url.indexOf("."))
((subject, teacher), 1)
})
//先触发一次Action,将学科的数量计算出来,将计算好的结果收集到Driver端
val subjects: Array[String] = subjectTeacherAndOne.map(_._1._1).distinct().collect()
val reduced = subjectTeacherAndOne.reduceByKey(new SubjectPartitioner2(subjects), _ + _)
// 将数据保存到hdfs中
reduced.saveAsTextFile(args(2))
val res: RDD[((String, String), Int)] = reduced.mapPartitions(it => {
it.toList.sortBy(-_._2).take(2).iterator
})
println(res.collect().toBuffer)
sc.stop()
}
}
class SubjectPartitioner2(val subjects: Array[String]) extends Partitioner {
//学科名称 -> 分区编号
val rules = new mutable.HashMap[String, Int]()
var i = 0
for (sub <- subjects) {
rules(sub) = i
i += 1
}
override def numPartitions: Int = subjects.length
override def getPartition(key: Any): Int = {
val subject = key.asInstanceOf[(String, String)]._1
rules(subject)
}
}
import org.apache.spark.rdd.RDD
import org.apache.spark.{Partitioner, SparkConf, SparkContext}
import scala.collection.mutable
object TeacherTopN5 {
def main(args: Array[String]): Unit = {
val isLocal = args(0).toBoolean
val conf = new SparkConf().setAppName(this.getClass.getSimpleName)
if(isLocal) {
conf.setMaster("local[*]")
}
val sc = new SparkContext(conf)
//指定以后从哪里读取数据创建RDD
val lines = sc.textFile(args(1))
//
val topN = args(2).toInt
//对数据进行切分整理
val subjectTeacherAndCount = lines.map(line => {
val fields = line.split("/")
val url = fields(2)
val teacher = fields(3)
val subject = url.substring(0, url.indexOf("."))
((subject, teacher), 1)
}).reduceByKey(_+_)
//按照学科进行分组
val grouped: RDD[(String, Iterable[((String, String), Int)])] = subjectTeacherAndCount.groupBy(_._1._1)
//对迭代器中的数据进行排序
val res = grouped.mapValues(it => {
implicit val rules = Ordering[Int].on[((String, String), Int)](t => -t._2)
val sorter = new mutable.TreeSet[((String, String), Int)]()
it.foreach(e => {
//从迭代器中取出来放入到TreeSet
sorter.add(e)
if(sorter.size > topN) {
//移除最小的
val smallest = sorter.last
sorter -= smallest
}
})
//保存到treeSet中最终的数据就是想要的结果
sorter
})
println(res.collect().toBuffer)
sc.stop()
}
}
import org.apache.spark.rdd.RDD
import org.apache.spark.{SparkConf, SparkContext}
import scala.collection.mutable
object TeacherTopN6 {
def main(args: Array[String]): Unit = {
val isLocal = args(0).toBoolean
val conf = new SparkConf().setAppName(this.getClass.getSimpleName)
if(isLocal) {
conf.setMaster("local[*]")
}
val sc = new SparkContext(conf)
//指定以后从哪里读取数据创建RDD
val lines = sc.textFile(args(1))
val topN = args(2).toInt
//对数据进行切分整理
val subjectTeacherAndCount = lines.map(line => {
val fields = line.split("/")
val url = fields(2)
val teacher = fields(3)
val subject = url.substring(0, url.indexOf("."))
((subject, teacher), 1)
}).reduceByKey(_+_)
//按照学科进行分组
val grouped: RDD[(String, Iterable[((String, String), Int)])] = subjectTeacherAndCount.groupBy(_._1._1)
//对迭代器中的数据进行排序
val res = grouped.mapValues(it => {
val sorter = new mutable.TreeSet[OrderingBean]()
it.foreach(e => {
//从迭代器中取出来放入到TreeSet
sorter += new OrderingBean(e._1._1, e._1._2, e._2)
if(sorter.size > topN) {
//移除最小的
val smallest = sorter.last
sorter -= smallest
}
})
//保存到treeSet中最终的数据就是想要的结果
sorter
})
println(res.collect().toBuffer)
sc.stop()
}
}
以上就是spark中取TopN的多种方式,每个方式都各有特点,具体使用哪一个方式要根据实际情况进行使用,比如说数据量的大小,或者是否对运算速度有明确的要求,具体还是要根据场景进行使用的.