Scala比较器

两个特质

Scala提供两个特质(trait)Ordered与Ordering用于比较。其中,Ordered混入(mix)Java的Comparable接口,而Ordering则混入Comparator接口。众所周知,在Java中

  • 实现Comparable接口的类,其对象具有了可比较性;
  • 实现comparator接口的类,则提供一个外部比较器,用于比较两个对象

Ordered与Ordering的区别与之相类似:

  • Ordered特质定义了相同类型间的比较方式,但这种内部比较方式是单一的;
  • Ordered则是提供比较器模板,可以自定义多种比较方式。

实例

在项目中,我们常常会遇到排序(或比较)需求,比如:对一个Person类

case class Person(name: String, age: Int) {
  override def toString = {
    "name: " + name + ", age: " + age
  }
}

对于Person类,如何做让其对象具有可比较性呢?我们可使用Ordered对象的函数orderingToOrdered做隐式转换,但还需要组织一个Ordering[Person]的隐式参数:

implicit object PersonOrdering extends Ordering[Person] {
  override def compare(p1: Person, p2: Person): Int = {
    p1.name == p2.name match {
      case false => -p1.name.compareTo(p2.name)
      case _ => p1.age - p2.age
    }
  }
}

class Person(val name: String, val age: Int, val fv: Int) extends Ordered[Person]  {

  override def compare(that:Person): Int = {
    if(this.name == that.name) {
      this.age - that.age
    } else {
      -(this.age - that.age)
    }
  }

 
val p1 = new Person("rain", 13)
val p2 = new Person("rain", 14)

Collection Sort

在实际项目中,我们常常需要对集合进行排序。回到开篇的问题——如何对Person类的集合做指定排序呢?下面用List集合作为demo,探讨在scala集合排序。首先,我们来看看List的sort函数:

// scala.collection.SeqLike

 //若使用sortWith,则需要定义返回值为Boolean的比较函数:
def sortWith(lt: (A, A) => Boolean): Repr = sorted(Ordering fromLessThan lt)

 //若使用sortBy,也需要指定Ordering隐式参数
def sortBy[B](f: A => B)(implicit ord: Ordering[B]): Repr = sorted(ord on f)

 //若调用sorted函数做排序,则需要指定Ordering隐式参数
def sorted[B >: A](implicit ord: Ordering[B]): Repr = {

...
}

Spark自定义排序案例

案例一 自定义类

使用自定义类的时候注意下面三点:

  • 继承Ordered类,重写compare方法
  • 序列化(因为,在进行shuffle 的时候,需要经过网络传输,所以实现序列化接口)
  • 重写toString(可选,主要为了可以展示数据)
ackage cn.edu360.day5

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


object CustomSort1 {

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

    val conf = new SparkConf().setAppName("CustomSort1").setMaster("local[*]")

    val sc = new SparkContext(conf)

    //排序规则:首先按照颜值的降序,如果颜值相等,再按照年龄的升序
    val users= Array("laoduan 30 99", "laozhao 29 9999", "laozhang 28 98", "laoyang 28 99")

    //将Driver端的数据并行化变成RDD
    val lines: RDD[String] = sc.parallelize(users)

    //切分整理数据
    val userRDD: RDD[User] = lines.map(line => {
      val fields = line.split(" ")
      val name = fields(0)
      val age = fields(1).toInt
      val fv = fields(2).toInt
      //(name, age, fv)
      new User(name, age, fv)
    })

    //不满足要求
    //tpRDD.sortBy(tp => tp._3, false)

    //将RDD里面装的User类型的数据进行排序
    val sorted: RDD[User] = userRDD.sortBy(u => u)

    val r = sorted.collect()

    println(r.toBuffer)

    sc.stop()

  }

}


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) {
      this.age - that.age
    } else {
      -(this.fv - that.fv)
    }
  }

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

案例二

//定义一个类作为排序规则,由该类继承排序器,在sortby的时候将颜值和年龄传入该类,不会改变数据的格式,只会改变顺序。

package cn.edu360.day5

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


object CustomSort2 {

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

    val conf = new SparkConf().setAppName("CustomSort2").setMaster("local[*]")

    val sc = new SparkContext(conf)

    //排序规则:首先按照颜值的降序,如果颜值相等,再按照年龄的升序
    val users= Array("laoduan 30 99", "laozhao 29 9999", "laozhang 28 98", "laoyang 28 99")

    //将Driver端的数据并行化变成RDD
    val lines: RDD[String] = sc.parallelize(users)

    //切分整理数据
    val tpRDD: RDD[(String, Int, Int)] = lines.map(line => {
      val fields = line.split(" ")
      val name = fields(0)
      val age = fields(1).toInt
      val fv = fields(2).toInt
      (name, age, fv)
    })

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

    println(sorted.collect().toBuffer)

    sc.stop()

  }

}

//定义一个类作为排序规则,在sortby的时候将颜值和年龄传入该类
class Boy(val age: Int, val fv: Int) extends Ordered[Boy] with Serializable {

  override def compare(that: Boy): Int = {
    if(this.fv == that.fv) {
      this.age - that.age
    } else {
      -(this.fv - that.fv)
    }
  }
}

案例三:样例类

推荐使用case class的原因,主要是因为
1.case class默认实现序列化
2.自动重写了toString
3.不需要new,case class是多例的

package cn.edu360.day5

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


object CustomSort3 {

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

    val conf = new SparkConf().setAppName("CustomSort3").setMaster("local[*]")

    val sc = new SparkContext(conf)

    //排序规则:首先按照颜值的降序,如果颜值相等,再按照年龄的升序
    val users= Array("laoduan 30 99", "laozhao 29 9999", "laozhang 28 98", "laoyang 28 99")

    //将Driver端的数据并行化变成RDD
    val lines: RDD[String] = sc.parallelize(users)

    //切分整理数据
    val tpRDD: RDD[(String, Int, Int)] = lines.map(line => {
      val fields = line.split(" ")
      val name = fields(0)
      val age = fields(1).toInt
      val fv = fields(2).toInt
      (name, age, fv)
    })

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

    println(sorted.collect().toBuffer)

    sc.stop()

  }

}


case class Man(age: Int, fv: Int) extends Ordered[Man] {

  override def compare(that: Man): Int = {
    if(this.fv == that.fv) {
      this.age - that.age
    } else {
      -(this.fv - that.fv)
    }
  }
}

案例四:隐式转换

定义一个类,在调用sortby的时候,将比较对象的参数传入,然后再定义一个隐式转化,将该类隐式地转化为继承ordered的类
不改动原本的类

package cn.edu360.day5

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


object CustomSort4 {

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

    val conf = new SparkConf().setAppName("CustomSort4").setMaster("local[*]")

    val sc = new SparkContext(conf)

    //排序规则:首先按照颜值的降序,如果颜值相等,再按照年龄的升序
    val users= Array("laoduan 30 99", "laozhao 29 9999", "laozhang 28 98", "laoyang 28 99")

    //将Driver端的数据并行化变成RDD
    val lines: RDD[String] = sc.parallelize(users)

    //切分整理数据
    val tpRDD: RDD[(String, Int, Int)] = lines.map(line => {
      val fields = line.split(" ")
      val name = fields(0)
      val age = fields(1).toInt
      val fv = fields(2).toInt
      (name, age, fv)
    })

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

    println(sorted.collect().toBuffer)

    sc.stop()

  }

}


case class XianRou(age: Int, fv: Int)
package cn.edu360.day5

/**
  * Created by zx on 2017/10/10.
  */
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
      }
    }
  }
}

案例五:利用本身属性比较

sortBy()可以通过参数的位置与正负,来改变比较规则

package cn.edu360.day5

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

/**
  * Created by zx on 2017/10/10.
  */
object CustomSort5 {

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

    val conf = new SparkConf().setAppName("CustomSort5").setMaster("local[*]")

    val sc = new SparkContext(conf)

    //排序规则:首先按照颜值的降序,如果颜值相等,再按照年龄的升序
    val users= Array("laoduan 30 99", "laozhao 29 9999", "laozhang 28 98", "laoyang 28 99")

    //将Driver端的数据并行化变成RDD
    val lines: RDD[String] = sc.parallelize(users)

    //切分整理数据
    val tpRDD: RDD[(String, Int, Int)] = lines.map(line => {
      val fields = line.split(" ")
      val name = fields(0)
      val age = fields(1).toInt
      val fv = fields(2).toInt
      (name, age, fv)
    })

    //充分利用元组的比较规则,元组的比较规则:先比第一,相等再比第二个
    val sorted: RDD[(String, Int, Int)] = tpRDD.sortBy(tp => (-tp._3, tp._2))

    println(sorted.collect().toBuffer)

    sc.stop()

  }

}

JDBC RDD
JDBC介绍

JDBC API 允许用户访问任何形式的表格数据,尤其是存储在关系数据库中的数据。
JDBC(Java DataBase Connectivity)是Java和数据库之间的一个桥梁,是一个规范而不是一个实现,能够执行SQL语句。它由一组用Java语言编写的类和接口组成。各种不同类型的数据库都有相应的实现,本文中的代码都是针对MySQL数据库实现的。

JDBC API 允许用户访问任何形式的表格数据,尤其是存储在关系数据库中的数据。
执行流程:

  • 连接数据源,如:数据库。
  • 为数据库传递查询和更新指令。
  • 处理数据库响应并返回的结果

spark javaRdd 如何拆分RDD_spark

代码:

package cn.edu360.day5

import java.sql.{Connection, DriverManager}

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


object JdbcRddDemo {

  val getConn = () => {
    DriverManager.getConnection("jdbc:mysql://localhost:3306/bigdata?characterEncoding=UTF-8", "root", "123568")
  }


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

    val conf = new SparkConf().setAppName("JdbcRddDemo").setMaster("local[*]")

    val sc = new SparkContext(conf)

    //创建RDD,这个RDD会记录以后从MySQL中读数据

    //new 了RDD,里面没有真正要计算的数据,而是告诉这个RDD,以后触发Action时到哪里读取数据
    val jdbcRDD: RDD[(Int, String, Int)] = new JdbcRDD(
      sc,     //传入SparkContext
      getConn,     //建立连接
      "SELECT * FROM logs WHERE id >= ? AND id < ?",     //sql语句
      1,      //范围下界  ,指定条件的时候使用 ,填充问号
      5,    //范围上界
      2, //分区数量
      rs => {      //该参数是函数,输入是ResultSet,返回是任意类型,表示结果集
        val id = rs.getInt(1)
        val name = rs.getString(2)
        val age = rs.getInt(3)
        (id, name, age)
      }
    )

    //


    //触发Action
    val r = jdbcRDD.collect()

    println(r.toBuffer)

    sc.stop()


  }

}