原文鏈接: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()
總結:以上六種排序方法,第五種最簡單,也基本能滿足日常開發所需。