Spark-實現自定義排序的六種方法(Scala版本)

原文鏈接: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()

總結:以上六種排序方法,第五種最簡單,也基本能滿足日常開發所需。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章