Spark-实现自定义排序的六种方法(Scala版本)

9 篇文章 0 订阅
8 篇文章 0 订阅

原文链接:https://www.toutiao.com/i6845585556722680328/

在实际开发中经常需要对数据进行排序统计,Spark的sortBy以及SortByKEy算子并不能完全适用开发场景,需要我们自定义排序规则,例如如下数据:

Array("张三 16 98.3", "李四 14 98.3", "王五 34 100.0", "赵六 26 98.2", "田七 18 98.2")

包含三个字段的学生数据,(姓名,年龄,成绩),我们需要按照成绩进行降序排序,成绩相同的按照年龄进行升序排序。

首先需要将数据转化成RDD格式的:

val conf: SparkConf = new SparkConf().setAppName("StudentSort").setMaster("local[2]")
val context: SparkContext = new SparkContext(conf)

val studentArr: Array[String] = Array("张三 16 98.3", "李四 14 98.3", "王五 34 100.0", "赵六 26 98.2", "田七 18 98.2")

val linesRDD: RDD[String] = context.parallelize(studentArr)

本文介绍六种实现自定义排序的方法,其中1-4是在自定义类的基础上进行的,5-6是利用用元祖的排序规则进行实现的。

第一种:

自定义一个Student类,该类继承Ordered和Serializable,并且重写排序方法。将RDD中的数据处理成Student格式的,然后调用sortBy排序时会按照自定义类的排序规则进行排序。

自定义Student类:

/**
   * 自定义类,重写排序方法,继承Ordered类,重写compare方法
   * 还要继承Serializable类,序列化自定义类
   *
   * 类的构造参数用val声明,会自动生成getter方法,否则无法直接使用this.score调用score
   * @param name
   * @param age
   * @param score
   */
  class Student(val name:String, val age: Int, val score: Double) extends Ordered[Student] with Serializable {

    /**
     * 自定义排序标准: 按照score进行降序排序,若score一样比较age
     * @param that
     * @return
     */
    override def compare(that: Student): Int = {
      if(this.score == that.score){
        this.age - that.age
      } else if(this.score < that.score){
        1
      } else {
        -1
      }
    }

    override def toString: String = s"name: $name, age: $age, score: $score"
}

排序过程:

 // 构造Student对象RDD
 val studentRDD: RDD[Student] = linesRDD.map(line => {
      val fields: Array[String] = line.split(" ")
      val name: String = fields(0)
      val age: Int = fields(1).toInt
      val score: Double = fields(2).toDouble
      new Student(name, age, score)  // 数据为Student的对象
    })

 // 排序,排序规则是Student类中重写的compare方法
 val sorted: RDD[Student] = studentRDD.sortBy(u => u) 

 val collected: Array[Student] = sorted.collect()

第二种:

同样自定义一个Student类,和第一种方法不同的是,该类只是用于说明排序规则,并且Student中只需保留排序用到的属性就可以,name可以不出现在Student中。

linesRDD.map处理得到的RDD中的数据类型是RDD[(String, Int, Double)]即元祖,而不是RDD[Student]。
这种方法在sortedBy中传入的是一个排序规则,不会改变数据的格式,只会改变顺序。

Student类:

/**
   * 自定义类,重写排序方法,继承Ordered类,重写compare方法
   * 还要继承Serializable类,序列化自定义类
   *
   *该类中是定义的是排序规则,与排序无关的属性可以不出现
   * @param age
   * @param score
   */
  class Student(val age: Int, val score: Double) extends Ordered[Student] with Serializable {

    /**
     * 自定义排序标准: 按照score进行降序排序,若score一样比较age
     * @param that
     * @return
     */
    override def compare(that: Student): Int = {
      if(this.score == that.score){
        this.age - that.age
      } else if(this.score < that.score){
        1
      } else {
        -1
      }
    }

  }

排序过程:

// RDD中数据是元祖类型
    val studentsRDD: RDD[(String, Int, Double)] = linesRDD.map(line => {
      val fields: Array[String] = line.split(" ")
      val name: String = fields(0)
      val age: Int = fields(1).toInt
      val score: Double = fields(2).toDouble
      (name, age, score)
    })

    // 排序 (传入的是排序规则,并不改变数据格式,只改变排序顺序)
    // 这时候的Student可以不包含全部属性,仅包含需要排序的属性就可以,这里的类只是用来说明排序的规则
    // Student也必须要实现序列化
    val sorted: RDD[(String, Int, Double)] = studentsRDD.sortBy(stu => {
      new Student(stu._2, stu._3)
    })

    val collected: Array[(String, Int, Double)] = sorted.collect()

第三种:

在方案二上进行改进。

将Student定义为一个样例类case class,这样Student就不必继承Serializable来实现序列化。并且在sortBy算子中,也需要每次都要new一个对象。

Student类:

 /**
   * 自定义类为样例类,继承Ordered类,重写compare排序方法
   *不再需要继承Serializable序列化类
   *
   *同样只需保留与排序相关的属性
   * @param age
   * @param score
   */
  case class Student(age: Int, score: Double) extends Ordered[Student]{

    /**
     * 自定义排序标准: 按照score进行降序排序,若score一样比较age
     * @param that
     * @return
     */
    override def compare(that: Student): Int = {
      if(this.score == that.score){
        this.age - that.age
      } else if(this.score < that.score){
        1
      } else {
        -1
      }
    }

  }

排序过程:

//  RDD中数据是元祖类型
    val studentsRDD: RDD[(String, Int, Double)] = linesRDD.map(line => {
      val fields: Array[String] = line.split(" ")
      val name: String = fields(0)
      val age: Int = fields(1).toInt
      val score: Double = fields(2).toDouble
      (name, age, score)
    })

    // 排序 (传入的是排序规则,并不改变数据格式,只改变排序顺序)
    // 这时候的Student可以不是全部属性,仅包含需要排序的属性就可以,这里的类只是用来说明排序的规则
    // 因为Student是样例类,这里不需要new了
    val sorted: RDD[(String, Int, Double)] = studentsRDD.sortBy(stu => {
      Student(stu._2, stu._3)
    })

    val collected: Array[(String, Int, Double)] = sorted.collect()

第四种:

以上三种方案,自定义Student类中只能重写一次compare方法,也就是只能有一个排序规则。

为了实现多种排序方法,我们定义一个隐式转换,隐式转换中定义多个排序规则,在排序时只要import相应的排序规则就可以了。

为了不再在sortBy算子进行new操作,这里的Student自定义类同样为样例类,但是只定义类中包含的属性,不需要再继承Ordered类,重写compare排序方法了。

/**
   * 样例类,仅定义类,排序规则在隐式转化中实现
   * @param age
   * @param score
   */
  case class Student(age:Int, score: Double)

而排序规则由专门的Object来实现,并且可以在Object中定义多种规则:

import XXX

/**
 * 排序规则类,隐式转换实现排序规则,可以有多种排序规则
 */
object StudentSortRules {

  // 按照分数进行排序
  implicit object OrderingStudentScore extends Ordering[Student]{
    override def compare(x: Student, y: Student): Int = {
      if(x.score == y.score){
        x.age - y.age
      } else if(x.score < y.score){
        1
      } else {
        -1
      }

    }
  }

// 按照年龄进行排序
  implicit object OrderingStudentAge extends Ordering[Student]{
    override def compare(x: Student, y: Student): Int = {
      if(x.age == y.age){
       if(x.score > y.score){
         1
       }else{
         -1
       }
      } else {
        x.age - y.age
      }

    }
  }

}

在排序时,说明使用哪种排序规则即可:

//  RDD中数据是元祖类型
    val studentsRDD: RDD[(String, Int, Double)] = linesRDD.map(line => {
      val fields: Array[String] = line.split(" ")
      val name: String = fields(0)
      val age: Int = fields(1).toInt
      val score: Double = fields(2).toDouble
      (name, age, score)
    })

    // 排序 (传入的是排序规则,并不改变数据格式,只改变排序顺序)
    // 使用隐式转化,引入排序的规则
    import StudentSortRules.OrderingStudentScore
    val sorted: RDD[(String, Int, Double)] = studentsRDD.sortBy(stu => {
      Student(stu._2, stu._3)
    })

    val collected: Array[(String, Int, Double)] = sorted.collect()

以上四种是通过自定义类的方式实现的。对于一些简单的业务逻辑来说,我们可以使用元组的比较规则来进行排序。
元组的比较规则是:先比第一个元素,如果第一个元素相等,再比第二个。

第五种:可以在sortBy时改变元祖形态,生成新的元祖,利用新元祖的排序规则进行排序:

//  RDD中数据是元祖类型
    val studentsRDD: RDD[(String, Int, Double)] = linesRDD.map(line => {
      val fields: Array[String] = line.split(" ")
      val name: String = fields(0)
      val age: Int = fields(1).toInt
      val score: Double = fields(2).toDouble
      (name, age, score)
    })

    // 将需要排序的字段放到新的元祖中,利用元祖的比较规则:
   // 先比元祖的第一个,负号表示降序;若第一个相等,再比第二个
    val sorted: RDD[(String, Int, Double)] = studentsRDD.sortBy(stu => (-stu._3, stu._2))

    val collected: Array[(String, Int, Double)] = sorted.collect()

第六种:不改变元祖的形态,使用Ordering中的on方法改变元祖比较的规则:

// RDD中数据是元祖类型
    val studentsRDD: RDD[(String, Int, Double)] = linesRDD.map(line => {
      val fields: Array[String] = line.split(" ")
      val name: String = fields(0)
      val age: Int = fields(1).toInt
      val score: Double = fields(2).toDouble
      (name, age, score)
    })

    // 以隐式转化的方式 通过指定元祖的排序规则来进行排序
    // Ordering[(Double, Int)] 元祖比较的样式格式
    // [(String, Int, Double)]原始元祖样式格式
    // (t =>(-t._3, t._2)) 元祖内数据的数据比较规则,先比较第一个数据,负号表示降序,再比较第二个数据
    implicit val rules = Ordering[(Double, Int)].on[(String, Int, Double)](t =>(-t._3, t._2))
    val sorted: RDD[(String, Int, Double)] = studentsRDD.sortBy(stu => stu)

    val collected: Array[(String, Int, Double)] = sorted.collect()

总结:以上六种排序方法,第五种最简单,也基本能满足日常开发所需。

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: Spark中的自定义函数包括三种类型:udf、udaf和udtf。 1. udf(User-Defined Function):用户自定义函数,用于对DataFrame中的每个元素进行单独的处理,返回一个新的值。可以使用Scala、Java或Python编写。 2. udaf(User-Defined Aggregate Function):用户自定义聚合函数,用于对DataFrame中的一组元素进行聚合操作,返回一个新的值。可以使用Scala、Java或Python编写。 3. udtf(User-Defined Table-Generating Function):用户自定义表生成函数,用于将一行数据转换为多行数据,返回一个新的DataFrame。只能使用Scala或Java编写。 这些自定义函数可以帮助我们更好地处理数据,提高Spark的处理效率和灵活性。 ### 回答2: Spark大数据处理中一款极为流行的计算框架,自带的函数库(UDF)非常有限,无法满足大规模数据处理需求,因此需要 Spark 自定义函数(UDF)来弥补这一不足。自定义函数分为三种类型:UDF、UDAF、UDTF。 UDF(User-Defined Function)即用户自定义函数,是一种对 RDD 或 DataFrame 数据进行处理的自定义函数。使用 UDF,可以用编写的代码扩展 Spark 的现有函数库,使其支持更为复杂的操作,提高工作效率。使用 UDF 可以通过嵌套 SQL 或是反射来创建一个函数。UDF 主要通过 Spark SQL 来进行使用,对于 Python 程序员来说还有 UDF 对象模型。 UDAF(User-Defined Aggregation Function)即用户自定义聚合函数。UDAF 可以更好地封装用户自定义聚合函数过程,提高代码复用率,把整个聚合过程封装到一个函数中,便于调用和维护。通常使用 UDAF 构造聚合表达式并将其应用于 Spark SQL 查询。在使用聚合操作时,用户可以指定自定义函数,一般使用聚合函数配合 Spark SQL 或是 API 来使用。 UDTF(User-Defined Table-Generating Function)即用户自定义表格生成函数,可以将一个输入行拆分成多个输出行,还可以通过 UDTF 将一个输入列转化成多个输出列。UDTF 操作有助于负责多输出格式和分割的情况下,实现强大的集合任务文件解析和行转换。与 UDF 和 UDAF 类似,UDTF 可以在调用函数时使用 Apply 函数。UDTF 可以返回多个 Row 对象,并将其转换为新的 DataFrame。UDTF 可以将一行拆分成多行,进行数据拆分和处理的任务。 总而言之,自定义函数是一个非常强大的工具,可以扩展 Spark 的能力,提高计算效率和工作效率。通过三种类型的自定义函数(UDF、UDAF、UDTF),Spark 可以更方便地进行数据处理和分析,使这个框架具备更灵活的应用能力。 ### 回答3: Spark是一种分布式计算框架,其生态圈非常丰富。在Spark中,我们可以使用自定义函数(User Defined Function,简称UDF)、自定义聚合函数(User Defined Aggregate Function,简称UDAF)及自定义表生成函数(User Defined Table Generating Function,简称UDTF)来满足特定的需求。 UDF是Spark中最常用的自定义函数,特别适合对单个列或多个列进行简单转换的场景。UDF可以用Scala、Java或Python等语言来编写。在Scala或Java中定义UDF时,需要定义一个函数,并将它与SparkSession的udf()方法一起使用。在Python中,UDF的定义基于通用Python函数,使用Python的decorators来描述该函数的功能。 UDAF是用于聚合多个值的自定义函数。UDAF的好处是可以以两种不同的方式来使用:作为聚合函数或开窗函数。Spark提供了两种UDAF:typed aggregates和untyped aggregates。typed aggregates是一种类型安全的操作,可以通过将多个值组合在一起来处理。untyped aggregates是一种无类型的操作,需要我们自己来处理所有细节。 UDTF是用于生成几个结果表的自定义函数。在使用UDTF时,我们需要定义一个新的中间表来存储结果,然后将中间表传递给Spark SQL的from()方法,以创建最终结果。 无论使用哪种自定义函数,我们都需要考虑性能因素。因为我们的数据通常分布在多个计算节点上,所以不合理的计算可能会导致结果不准确或性能下降。另外,我们还需要确保我们的自定义函数能够处理大型数据集,并且具有足够的容错能力。 总之,Spark中的自定义函数可以帮助我们实现一些常规操作以外的数据处理需求。通过UDF、UDAF和UDTF,我们可以根据具体的场景设计出高效、可靠的数据处理方案。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值