Spark实现自定义排序

需求:
对于有复杂排序条件的需求,可以利用自定义排序来实现,同时可以使用多种方案实现自定义排序需求。

对指定的数据(字段分别为:名称 年龄 颜值,数据以空格分割),按照指定的要求排序,排序要求为:根据颜值降序,如果颜值相同,再按照年龄升序排序




先看下面代码:

object DefinitionSort {
  def main(args: Array[String]): Unit = {

    // 设定spark计算框架的运行(部署) 环境
    val conf: SparkConf = new SparkConf().setMaster("local").setAppName("JSONApp")
    // 创建Spark上下文对象
    val sc = new SparkContext(conf)

    val users: Array[String] = Array("laoduan 30 99", "laozhao 29 9999", "laozhang 28 98", "laoyang 28 99")
    val lines: RDD[String] = sc.makeRDD(users)

    val tpRDD: RDD[(String, String, String)] = lines.map {
      line => {
        val fields: Array[String] = line.split(" ")
        // 姓名
        val name: String = fields(0)
        // 年龄
        val age: String = fields(1)
        // 颜值
        val fv: String = fields(2)
        (name, age, fv)
      }
    }
    // 默认为true, 就是升序,   false为降序
    val sortTpRDD: RDD[(String, String, String)] = tpRDD.sortBy(tp => tp._3, false)
    sortTpRDD.collect().foreach(println)
  }
}

结果为:
(laozhao,29,9999)
(laoduan,30,99)
(laoyang,28,99)
(laozhang,28,98)

可以看到上面的代码是按照颜值的顺序来进行排序的, 如果有更复杂的业务要求, 很显然按照一个属性值来进行排序是不够的!


如果现在有个排序规则:

排序规则: 首先按照颜值进行降序, 如果颜值相等, 再按照年龄进行升序

那么现在该如何来排序? 代码如下:

方案一

把数据封装成类或者case class,然后类继承Ordered[类型] ,然后可以自定义排序规则。
这种处理方式,返回值类型是类的实例对象。
// 自定义排序演示
object DefinitionSort {
  def main(args: Array[String]): Unit = {

    // 设定spark计算框架的运行(部署) 环境
    val conf: SparkConf = new SparkConf().setMaster("local").setAppName("JSONApp")
    // 创建Spark上下文对象
    val sc = new SparkContext(conf)

    //排序规则: 首先按照颜值进行降序, 如果颜值相等, 再按照年龄进行升序
    val users: Array[String] = Array("laoduan 30 99", "laozhao 29 9999", "laozhang 28 98", "laoyang 28 99")
    val lines: RDD[String] = sc.makeRDD(users)

    val userRDD: RDD[User] = lines.map {
      line => {
        val fields: Array[String] = line.split(" ")
        // 姓名
        val name: String = fields(0)
        // 年龄
        val age: Int = fields(1).toInt
        // 颜值
        val fv: Int = fields(2).toInt
        new User(name, age, fv)
      }
    }
    // 排序规则: 首先按照颜值进行降序, 如果颜值相等, 再按照年龄进行升序
    val sorted: RDD[User] = userRDD.sortBy(u => u)
    sorted.collect().foreach(println)
  }
}

// 自定义排序规则
class User(val name: String, val age: Int, val fv: Int) extends Ordered[User] with Serializable {
  override def compare(that: User): Int = {
     // 如果颜值不相等, 则按照颜值的大小来进行降序
     if (this.fv - that.fv != 0){
       // 按照fv降序
         return  -(this.fv - that.fv)
     }else {
       // 按照age升序
        return  (this.age - that.age)
     }
  }
  // 重写toString方法. 如果不重写toString方法, 执行sorted.collect().foreach(println)会打印出 对象实例值, 而不会打印name,age, fv值
  override def toString: String = s"name : $name, age : $age,  fv: $fv"
}

结果为:
name : laozhao, age : 29,  fv: 9999
name : laoyang, age : 28,  fv: 99
name : laoduan, age : 30,  fv: 99
name : laozhang, age : 28,  fv: 98

上面代码, 把name, age, fv传入到了User对象里, User对象里自定义了排序规则, 在User有着自己的排序规则. 当执行val sorted: RDD[User] = userRDD.sortBy(u => u), sortBy算子会根据RDD里每个User对象,每个User对象里的排序规则都是相同的, 同时user对象里封装了数据, sortBy算子会根据User对象里的排序规则会对每个数据进行排序. 两对象之间里的数据根据User对象里的排序规则相互进行排序. 最终达到想要的结果!



方案二:

对原始数据不进行封装,仅仅在排序的时候,利用class或者case class指定排序的规则。
返回值的结果类型:还是原来的数据类型。和类本身无关。仅仅是利用类的规则来实现了排序
// 自定义排序演示
object DefinitionSort1 {
  def main(args: Array[String]): Unit = {

    // 设定spark计算框架的运行(部署) 环境
    val conf: SparkConf = new SparkConf().setMaster("local").setAppName("JSONApp")
    // 创建Spark上下文对象
    val sc = new SparkContext(conf)

    // 排序规则: 首先按照颜值进行降序, 如果颜值相等, 再按照年龄进行升序
    val users: Array[String] = Array("laoduan 30 99", "laozhao 29 9999", "laozhang 28 98", "laoyang 28 99")
    val lines: RDD[String] = sc.makeRDD(users)

    val tpRDD: RDD[(String, Int, Int)] = lines.map {
      line => {
        val fields: Array[String] = line.split(" ")
        // 姓名
        val name: String = fields(0)
        // 年龄
        val age: Int = fields(1).toInt
        // 颜值
        val fv: Int = fields(2).toInt
        (name, age, fv)
      }
    }
    // 排序(传入了一个排序规则, 不会改变数据的格式, 只会改变顺序)
    val sorted: RDD[(String, Int, Int)] = tpRDD.sortBy(tp => new Boy(tp._2, tp._3))
    sorted.collect().foreach(println)

  }
}
class Boy(val age: Int, val fv: Int) extends Ordered[Boy] with Serializable {
  override def compare(that: Boy): Int = {
     // 如果颜值不相等, 则按照颜值的大小来进行降序
     if (this.fv - that.fv != 0){
         // 按照fv降序
         return  -(this.fv - that.fv)
     }else {
        // 按照age升序
        return  (this.age - that.age)
     }
  }
}
结果为:
name : laozhao, age : 29,  fv: 9999
name : laoyang, age : 28,  fv: 99
name : laoduan, age : 30,  fv: 99
name : laozhang, age : 28,  fv: 98

上面的代码, map算子放回一个tuples, 当执行val sorted: RDD[(String, Int, Int)] = tpRDD.sortBy(tp => new Boy(tp._2, tp._3)), 会把每个tuple里的数据放到User对象里, 在这里User对象相当于一个排序规则, 把数据放进去,会放回出结果来, 相当于容器, 把数据放进去会返回结果来的容器, 可以看到sorted变量的类型还是RDD[(String, Int, Int)], 并没有变成RDD[User]类型, User对象在这里处于 中间传递的作用, 目的是让数据根据排序规则进行排序. 最后将其输出.
注意: 在这里并不用重写toString方法, 因为把数据传递给user对象,返回还是之前的数据类型, 并没有返回user对象, 就不需要重写toStriing方法. 还是要继承Serializable.



方案三: 利用样例类来实现

如果是class,需要实现序列化特质,Serializable,如果是case class,可以不实现该序列化特质。

// 自定义排序演示
object DefinitionSort2 {
  def main(args: Array[String]): Unit = {

    // 设定spark计算框架的运行(部署) 环境
    val conf: SparkConf = new SparkConf().setMaster("local").setAppName("JSONApp")
    // 创建Spark上下文对象
    val sc = new SparkContext(conf)

    //排序(传入了一个排序规则, 不会改变数据的格式, 只会改变顺序)
    val users: Array[String] = Array("laoduan 30 99", "laozhao 29 9999", "laozhang 28 98", "laoyang 28 99")
    val lines: RDD[String] = sc.makeRDD(users)

    val tpRDD: RDD[(String, Int, Int)] = lines.map {
      line => {
        val fields: Array[String] = line.split(" ")
        // 姓名
        val name: String = fields(0)
        // 年龄
        val age: Int = fields(1).toInt
        // 颜值
        val fv: Int = fields(2).toInt
        (name, age, fv)
      }
    }

    // 排序(传入了一个排序规则, 不会改变数据的格式, 只会改变顺序)
    val sorted: RDD[(String, Int, Int)] = tpRDD.sortBy(tp => Man(tp._2, tp._3))
    sorted.collect().foreach(println)

  }
}
// 样例类
case class Man(age: Int, fv: Int) extends Ordered[Man] {
  override def compare(that: Man): Int = {
     // 如果颜值不相等, 则按照颜值的大小来进行降序
     if (this.fv - that.fv != 0){
         // 按照fv降序
         return  -(this.fv - that.fv)
     }else {
        // 按照age升序
        return  (this.age - that.age)
     }
  }
}

结果为:
name : laozhao, age : 29,  fv: 9999
name : laoyang, age : 28,  fv: 99
name : laoduan, age : 30,  fv: 99
name : laozhang, age : 28,  fv: 98


方案四: 利用隐式转换

利用隐式转换时,类可以不实现Ordered的特质,普通的类或者普通的样例类即可。
隐式转换支持,隐式方法,隐式函数,隐式的object和隐式的变量,
如果都同时存在,优先使用隐式的object,隐式方法和隐式函数中,会优先使用隐式函数。
隐式转换可以写在任意地方(当前对象中,外部的类中,外部的对象中),如果写在外部,需要导入到当前的对象中即可。
我在另一个伴生对象中里写隐式转换
package test.zhang.shao

object SortRules {
   implicit object OrderingXiaoRou extends Ordering[XianRou] {
     override def compare(x: XianRou, y: XianRou): Int = {
       if (x.fv == y.fv){
         x.age - y.age
       }else {
         y.fv - x.fv
       }
     }
   }
}

在另一个伴生对象中导入该隐式转换

package test.zhang.shao

import org.apache.spark.rdd.RDD
import org.apache.spark.{SparkConf, SparkContext}

// 自定义排序演示
object DefinitionSort3 {
  def main(args: Array[String]): Unit = {

    // 设定spark计算框架的运行(部署) 环境
    val conf: SparkConf = new SparkConf().setMaster("local").setAppName("JSONApp")
    // 创建Spark上下文对象
    val sc = new SparkContext(conf)

    //排序(传入了一个排序规则, 不会改变数据的格式, 只会改变顺序)
    val users: Array[String] = Array("laoduan 30 99", "laozhao 29 9999", "laozhang 28 98", "laoyang 28 99")
    val lines: RDD[String] = sc.makeRDD(users)

    val tpRDD: RDD[(String, Int, Int)] = lines.map {
      line => {
        val fields: Array[String] = line.split(" ")
        // 姓名
        val name: String = fields(0)
        // 年龄
        val age: Int = fields(1).toInt
        // 颜值
        val fv: Int = fields(2).toInt
        (name, age, fv)
      }
    }
    // 排序(传入了一个排序规则, 不会改变数据的格式, 只会改变顺序)
    //  如果把隐式转换写在其他的object中,就使用import a._  如果是写在其他的类中,val obj = new 类()  import obj._

    import SortRules.OrderingXiaoRou
    val sorted: RDD[(String, Int, Int)] = tpRDD.sortBy(tp => XianRou(tp._2, tp._3))
    sorted.collect().foreach(println)

  }
}

case class XianRou(age: Int, fv: Int){
}

结果为:
name : laozhao, age : 29,  fv: 9999
name : laoyang, age : 28,  fv: 99
name : laoduan, age : 30,  fv: 99
name : laozhang, age : 28,  fv: 98



方案五: 利用元组封装排序条件
最简单的实现方案,直接利用元组来封装要排序的条件,默认升序,降序使用-号即可

// 自定义排序演示
object DefinitionSort4 {
  def main(args: Array[String]): Unit = {

    // 设定spark计算框架的运行(部署) 环境
    val conf: SparkConf = new SparkConf().setMaster("local").setAppName("JSONApp")
    // 创建Spark上下文对象
    val sc = new SparkContext(conf)

    //排序(传入了一个排序规则, 不会改变数据的格式, 只会改变顺序)
    val users: Array[String] = Array("laoduan 30 99", "laozhao 29 9999", "laozhang 28 98", "laoyang 28 99")
    val lines: RDD[String] = sc.makeRDD(users)

    val tpRDD: RDD[(String, Int, Int)] = lines.map {
      line => {
        val fields: Array[String] = line.split(" ")
        // 姓名
        val name: String = fields(0)
        // 年龄
        val age: Int = fields(1).toInt
        // 颜值
        val fv: Int = fields(2).toInt
        (name, age, fv)
      }
    }
    
    val sorted: RDD[(String, Int, Int)] = tpRDD.sortBy(tp => (-tp._3, tp._2))
    sorted.collect().foreach(println)
  }
}

结果为:
(laozhao,29,9999)
(laoyang,28,99)
(laoduan,30,99)
(laozhang,28,98)

上面的代码:val sorted: RDD[(String, Int, Int)] = tpRDD.sortBy(tp => (-tp._3, tp._2)), -tp._3表示降序, tp._2表示升序,默认为升序. 先按照颜值排序, 从大到小, 如果颜值相同, 则按照年龄从小到大.




方案六:利用Ordering的on方法

无需借助任何的类或者对象

只需要利用Ordering特质的on方法即可

// 自定义排序演示
object DefinitionSort4 {
  def main(args: Array[String]): Unit = {

    // 设定spark计算框架的运行(部署) 环境
    val conf: SparkConf = new SparkConf().setMaster("local").setAppName("JSONApp")
    // 创建Spark上下文对象
    val sc = new SparkContext(conf)

    //排序(传入了一个排序规则, 不会改变数据的格式, 只会改变顺序)
    val users: Array[String] = Array("laoduan 30 99", "laozhao 29 9999", "laozhang 28 98", "laoyang 28 99")
    val lines: RDD[String] = sc.makeRDD(users)

    val tpRDD: RDD[(String, Int, Int)] = lines.map {
      line => {
        val fields: Array[String] = line.split(" ")
        // 姓名
        val name: String = fields(0)
        // 年龄
        val age: Int = fields(1).toInt
        // 颜值
        val fv: Int = fields(2).toInt
        (name, age, fv)
      }
    }
    /** tp=>(-tp._3,tp._2)  具体的排序的规则
      * Ordering[T].on[U](f)
      * U   (String,Int,Int)  原始的数据类型
      * T   (Int,Int)        具体的函数的返回值的类型
      */
    implicit  val obj = Ordering[(Int, Int)].on[(String, Int, Int)](tp => (-tp._3, tp._2))
    val sorted: RDD[(String, Int, Int)] = tpRDD.sortBy(tp => tp)
    sorted.collect().foreach(println)

  }
}

结果为:
(laozhao,29,9999)
(laoyang,28,99)
(laoduan,30,99)
(laozhang,28,98)

Ordering[(Int, Int)] 是最终比较的规则格式, on[(String, Int, Int)]是比较之前的数据格式 tp=>(-tp._3,tp._2) 是 转换成想要的比较格式(排序)的规则.根据业务要求来转换成想要的排序规则.



参考文章:
https://blog.csdn.net/qq_21439395/article/details/80200790


参考视频链接:
https://www.bilibili.com/video/av50118777?p=51

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值