Scala高级特性
1.课程目标
- 深入理解高阶函数 闭包函数 柯里化函数
- 深入理解隐式方法 隐式变量以及隐式参数
- 综合案例 模拟Spark任务调度
2.Scala中函数
2.1高阶函数
-
函数是一个对象,如果一个方法的参数包含函数对象,那么这个方法就叫做高阶函数或者高阶方法 格式: def 方法名称((输入数据)=>{输出数据}):返回值类型={ 方法体 } /** * 函数是一个对象,如果一个方法的参数包含函数对象,那么这个方法就叫做高阶函数或者高阶方法 * * 格式: * def 方法名称((输入数据)=>{输出数据}):返回值类型={ * 方法体 * } */ class Number(val x: Int) { //高阶函数 def compute(f: Int => Int): Int = { f(x) } def square(x: Int): Int = { x * x } } object Scala_01_gaojie { def main(args: Array[String]): Unit = { val number = new Number(10) //求平方 val f: Int => Int = (x: Int) => x * x println(number.compute(f)) //方法的功能依赖于函数参数 //求立方 val f2: Int => Int = (x: Int) => x * x * x println(number compute f2) } }
-
/** * ShopCar 类 * 数组 Arrybuffer[Double] 装商品 * 方法 add 添加商品价格功能 * 方法:计算总价 (参数:函数 功能实现打折) */ class ShopCar { val goods = ArrayBuffer[Double]() def add(g: Double): Unit = { goods += g } //结算 def account(f: Double => Double): Double = { //1.计算所有商品的总价 var sum: Double = 0.0 goods.foreach((x: Double) => sum = sum + x) //2.通过函数进行打折 f(sum) } } object Scala_02_gaojie { def main(args: Array[String]): Unit = { val car = new ShopCar car add 10 car add 50 car add 88.88 //打折 满100 减50 满80 减20 val f: Double => Double = (sum: Double) => { if (sum >= 100) { sum - 50 } else if (sum >= 80) { sum - 20 } else { sum } } val result=car account f println(s"消费:$result") } }
2.2高阶函数使用规则
-
以Array.foreach 为例 说明函数的使用规则
-
object Scala_03_gaojie { def main(args: Array[String]): Unit = { val arr = Array[Int](1, 4, 5, 8, 9) //第一种遍历方式 val f = (x: Int) => println(x) arr.foreach(f) println("--------------") //第二种方式 使用匿名函数 arr.foreach((x: Int) => println(x)) println("--------------") //第三种方式 省略数据类型 匿名函数 arr.foreach(x => println(x)) println("-----------------") //第四中方式 _ 占位符 arr.foreach(println(_)) println("-----------------") //第五中方式 省略占位符 省略小括号 arr.foreach(println) println("-----------------") //map操作省略形式 arr.map(_*2).foreach(println) println("-----------------") // arr.map(x=>x*2) arr.filter(_%2==0).foreach(println) //reduce 操作 println(arr.reduce(_+_)) //arr.reduce((x:Int,y:Int)=>x+y) } }
2.3闭包函数
-
一个函数如果使用了外部变量,则这个函数就叫做闭包函数 val a=10 val f:Int=> Int =(x:Int)=>a+x //方法内部,主要用来减少函数参数传递的 val f2:(Int,Int)=>Int =(x:Int,y:Int)=x+y 闭包函数:减少函数参数列表,是的函数操作相对更为简单
2.4柯里化函数
-
概念:将一个具有复杂(多个)参数列表的方法转换成具有(多个简单参数列表)的方法,这个过程就叫做柯里化过程 ,具有多个简单参数列表的方法 ,就叫做柯里化方法 柯里化函数: 柯里化函数是函数的级联操作 柯里化函数实际上将每一步操作都返回一个function,也就说柯里化是将函数作为了方法的返回值 def add(x:Int,y:Int)=x+y //正常方法 def add(x:Int)(y:Int)=x+y //柯里化方法 class Number2(val x: Int) { //高阶函数 def compute(f: Int => Int): Int = { f(x) } } object Scala_05_currying { def add(x: Int, y: Int) = x + y //正常方法 /** * 如果一个方法具备多个参数列表,则可以分步骤进行操作 * 如果参数是准备不完整的,我们可就通过柯里化进行延迟操作 * * @param x * @param y * @return */ def add2(x: Int)(y: Int) = x + y //柯里化方法 def add3(x: Int)(y: Int)(z: Int) = x + y + z def add4(x:Int) = (y:Int)=>x+y def main(args: Array[String]): Unit = { println(add(1, 2)) println(add2(1)(2)) //柯里化方法分步操作 val f = add2(1) _ //返回值是函数 function1 println(f.toString()) val result = f(2) println(result) println("-------------------") val number = new Number2(2) println(number.compute(f)) println("-------------------") //add3 分步骤操作 val f3=add3(1) _ //function1 println(f3.toString()) val f4=f3(2) // function1 println(f4) val result2=f4(3) //6 println(result2) //柯里化函数: 柯里化函数是函数的级联操作 val f5=add4(1)(2) //function1 } }
2.5柯里化应用场景
-
统计就业率 公式:已工作人数/班级人数 定义: def workRate(worker:Int,sum:Int)=worker.toDouble/sum.toDouble def workRate2(sum:Int)(worker:Int)=worker.toDouble/sum.toDouble object Scala_06_currying { def workRate(worker: Int, sum: Int) = worker.toDouble / sum.toDouble def workRate2(sum: Int)(worker: Int) = worker.toDouble / sum.toDouble def main(args: Array[String]): Unit = { //需求 多次统计 7, 10 ,15 ,30,90 班级人数 100 // 就业人数:50 80 90 98 100 println( workRate(50,100)) println( workRate(80,100)) println( workRate(90,100)) println( workRate(98,100)) println( workRate(100,100)) println("---------------------------------") //使用柯里化方法 规避掉冗余数据 val f=workRate2(100) _ println(f(50)) println(f(80)) println(f(90)) println(f(98)) println(f(100)) } } 柯里化函数能够减少方法参数的传递
3. 隐式转换操作
-
格式: 能够将源数据类型转换成目标数据类型 implicit def 名称(源数据类型) =目标类型对象 注意:隐式转换方法只能存在于object中 class Dog { def run(): Unit = { println("飞奔吧小狗") } } class Teacher { def readScala(): Unit = { println("教学scala") } } class Bird { def fly(): Unit = { println("飞上天") } } object Scala_07_implicit { //implict def 名称(源数据类型) =目标类型对象 implicit def dogToTeacher(d: Dog) = new Teacher //让狗飞上天 implicit def dogToBird(d: Dog) = new Bird def main(args: Array[String]): Unit = { val dog = new Dog dog.run() dog.readScala() dog.fly() } }
3.1 隐式转换的对象类型
-
总结: 1.隐式转换方法可以将一个对象转换成函数对象 2.隐式转换方法可以将一个对象转换成单例对象Object 3.具有继承关系的子类可以继承父类的隐式转换功能 import java.io.File import scala.io.Source class Person class Sun extends Person object Scala_08_implicit { //person能否通过隐式转换将函数 implicit def personToFunction(p: Person) = (x: Int) => x * x //person对象能否转换成object对象呢 Source implicit def personToSource(p: Person) = Source def main(args: Array[String]): Unit = { val person = new Person println(person(10)) println(person.fromFile(new File("D:/a.txt")).getLines().mkString(",")) val sun = new Sun println(sun(100)) //10000 println(sun.fromFile(new File("D:/a.txt")).getLines().mkString(",")) } }
3.2隐式参数和隐式变量
-
隐式变量: implicit val/var 变量名称=值 隐式参数 :修饰参数列表 implicit只能用在参数列表的最前边 def 方法名称(implicit 参数列表) 总结:隐式变量可以给隐式参数提供默认参数。方法保留原始功能 object Scala_09_implicit { //implicit val/var 变量名称=值 implicit val a: Int = 100 implicit val b: Long = 1000 //implicit val c: Int = 80 //def 方法名称(implicit 参数列表) def run(implicit speed: Int, time: Long): Unit = { println(s"以${speed}跑 时间${time}") } def main(args: Array[String]): Unit = { run run(90,9000) } }
3.3 隐式转换的管理
-
一般情况,会将所有的隐式转换操作 都放在统一object中, 使用时需要进行导入 import object._ //代表导入所有 import object.名称 //名称可以是方法名称也可以是变量名称 object CommonImplicitObject { //implict def 名称(源数据类型) =目标类型对象 implicit def dogToTeacher(d: Dog) = new Teacher //让狗飞上天 implicit def dogToBird(d: Dog) = new Bird //implicit val/var 变量名称=值 implicit val a: Int = 100 implicit val b: Long = 1000 implicit val c: Int = 80 }
3.4隐式转换的查找机制
-
查找机制: 1.在当前隐式转换操作的文件或者object中进行查找 2.他在源数据类型的伴生对象中进行查找 class Person10 { def say() = println("说话") } class Dog10 { } object Dog10 { implicit def dogToPerson(d: Dog10) = new Person10 } object Scala_10_implicit { def main(args: Array[String]): Unit = { val dog1 = new Dog10 dog1.say() } }
4.编程实战
-
小目标:实现基于akka的spark底层通信机制
-
Spark
- spark是基于内存的分布式大数据处理框架。主/多从
-
akka
- akka是基于scala 开发的轻量级RPC通信框架 。高可用、高性能、可扩展。
-
akka编程模型Actor
- 可以发送和接受消息
- 多个actor可以并发执行
- akka中actor能够创建和管理其他的actor
- actor轻量级的编程模型,1GB=百万级的actor,支持高并发。
-
akka的两个核心编程类
- ActorSystem
- ActorSystem创建并且管理所有Actor,并且可以发送消息。在一个进程ActorSystem是单例存在的
- Actor
- preStart:在actor初始化(调用构造函数)完成之后直接调用的方法。做一些初始化工作逻辑,还有启动定时任务操作。该方法在整个actor声明周期只调用一次
- receiver:接受消息并且根据消息进行相应的逻辑处理的。receiver可以循环接受消息
想要基于akka的编程,首先创建的ActorSystem对象,要想基于akka的逻辑处理就必须创建actor。这里actor是有ActorSystem创建和管理的
- ActorSystem
-
-
基于akka的两台终端进行互相通信
-
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Rglp5uR9-1592701249067)(C:\Users\tp\Desktop\23期scala\scala_day03\资料\两台机器互联图.png)]
-
master代码实现
-
package cn.itcast import akka.actor.{Actor, ActorSystem, Props} import com.typesafe.config.{Config, ConfigFactory} /** * 定义actor类 */ class Master extends Actor { override def receive: Receive = { case "test" => println("测试成功") case "slaver_regist" => { sender ! "SUC" } } } object Master { def main(args: Array[String]): Unit = { val host = "192.168.41.41" //IP地址要修改成你们本机IP地址 val port = 8888 val configStr = s""" |akka.actor.provider="akka.remote.RemoteActorRefProvider" |akka.remote.netty.tcp.hostname="$host" |akka.remote.netty.tcp.port="$port" """.stripMargin val config = ConfigFactory.parseString(configStr) //创建actorSystem对象 val masterActorSystem = ActorSystem.apply("masterActorSystem", config) //创建actor 自动启动actor val masterActor = masterActorSystem.actorOf(Props(new Master), "masterActor") masterActor ! "test" //akka.tcp://masterActorSystem@192.168.41.41:8888 在该链接之下,ActorSystem管理所有的acotr //也就是说 如果我们想要在其他进程中获取masterActor,则必须通过协议接口 } }
-
-
slaver代码实现
-
package cn.itcast import akka.actor.{Actor, ActorSystem, Props} import com.typesafe.config.ConfigFactory class Slaver extends Actor { //创建完成后向master发送消息 override def preStart(): Unit = { //需要获取masterAcotr对象 val masterActor = context.actorSelection("akka.tcp://masterActorSystem@192.168.41.41:8888/user/masterActor") masterActor ! "slaver_regist" } override def receive: Receive = { case "slaver_test" => println("slaver_test 测试成功") case "SUC" => println("注册成功") } } object Slaver { def main(args: Array[String]): Unit = { val host = "192.168.41.41" //IP地址要修改成你们本机IP地址 val port = 9999 val configStr = s""" |akka.actor.provider="akka.remote.RemoteActorRefProvider" |akka.remote.netty.tcp.hostname="$host" |akka.remote.netty.tcp.port="$port" """.stripMargin val config = ConfigFactory.parseString(configStr) //创建ActorSystem val slaverActorSystem = ActorSystem.apply("slaverActorSystem", config) //创建actor val slaverActor = slaverActorSystem.actorOf(Props(new Slaver), "slaverActor") slaverActor ! "slaver_test" } }
-
4.1 基于akka模拟实现spark的通信机制
-
业务场景
- master端:负责接收worker端的注册,并且管理worker。在管理过程总,能够自动检测worker状态,查看worker是否超时。如果worker超时则直接剔除掉,接受worker的心跳信息,并且更新worker状态
- worker端:负责向master注册,并且提交其资源信息(cpu和内存),worker需要实时的向master发送心跳信息。
-
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-M57oU93Z-1592701249069)(C:\Users\tp\Desktop\23期scala\scala_day03\资料\Spark集群图.png)]
-
分析业务模型:结合样例类和样例对象
- 样例对象 CheckTimeOut
- 样例类 Registed(msg:String) master反馈给worker信息
- 样例类 WorkerRegist(workInto:WorkInfo) worker向master注册的信息
- 类:WorkInfo(包含了worker资源信息(cpu和内存,workerId)) 资源对象
- 样例对象 HeartBeat worker内部定时任务的标志
- 样例类 HeartBeatMsg(workerId:String) 向master发送的心跳信息
-
实现
-
master实现
-
package cn.itcast.master import akka.actor.{Actor, ActorSystem, Props} import cn.itcast.common._ import com.typesafe.config.ConfigFactory import scala.collection.mutable import scala.concurrent.duration._ class Master extends Actor { val interval_time = 3000 //master用来管理woker信息的 val workerMap = mutable.Map[String, WorkInfo]() val TIME_OUT = 5000 //超时时长 override def preStart(): Unit = { //actor启动立即启动定时任务 //定时任务定义 import context.dispatcher //四个参数: // 1.延迟多长时间开始启动 定时任务 //2. 间隔多长时间执行一次定时任务 //3. Actor对象 //4. 消息 context.system.scheduler.schedule(0 millis, interval_time millis, self, CheckTimeOut) } override def receive: Receive = { case CheckTimeOut => { //检查worker的状态,worker是否超时 for (t <- workerMap) { val workInfo = t._2 if (System.currentTimeMillis() - workInfo.WORK_STATE > TIME_OUT) { workerMap -= t._1 } } println("------------存活的worker信息---------------------") //打印存活的worker信息 workerMap.foreach(t => println(t._2.toString)) } //处理worker的注册 case WorkerRegist(workInfo) => { //判断worker是否已经注册 if (!workerMap.contains(workInfo.workerId)) { workInfo.WORK_STATE = System.currentTimeMillis() //直接更新状态 workerMap += (workInfo.workerId -> workInfo) //注册成功了 sender ! Registed("SUC") } } //接受worker端的心跳信息 case HeartBeatMsg(workId) => { //更新worker的状态 val workInfo: WorkInfo = workerMap.get(workId).get workInfo.WORK_STATE = System.currentTimeMillis() } } } object Master { def main(args: Array[String]): Unit = { val host = "192.168.41.41" val port = 8888 val cfgStr = s""" |akka.actor.provider="akka.remote.RemoteActorRefProvider" |akka.remote.netty.tcp.hostname="$host" |akka.remote.netty.tcp.port="$port" """.stripMargin val config = ConfigFactory.parseString(cfgStr) //创建ActorSytem Actor对象 val masterActorSystem = ActorSystem.apply("masterActorSystem", config) //创建Actor对象 val masterActor = masterActorSystem.actorOf(Props(new Master), "masterActor") } }
-
worker端实现
-
package cn.itcast.worker import java.util.UUID import akka.actor.{Actor, ActorSelection, ActorSystem, Props} import cn.itcast.common._ import cn.itcast.master.Master import com.typesafe.config.ConfigFactory import scala.concurrent.duration._ class Worker extends Actor { //workerID val wid = UUID.randomUUID().toString val cpu = 24 + (math.random * 100).toInt val memory = 32 + (math.random * 100).toInt var masterActor: ActorSelection = _ //向master进行注册 override def preStart(): Unit = { val workInfo = new WorkInfo(wid, cpu, memory) //获取masteractor 发送消息 masterActor = context.actorSelection("akka.tcp://masterActorSystem@192.168.41.41:8888/user/masterActor") masterActor ! WorkerRegist(workInfo) } override def receive: Receive = { //worker端接受到master的反馈信息 case Registed(msg) => { println("注册成功") //启动定时任务 import context.dispatcher context.system.scheduler.schedule(0 millis, 3000 millis, self, HeartBeat) } //接受心跳信息HeartBeat case HeartBeat => { //给master发送心跳信息 masterActor ! HeartBeatMsg(wid) } } } object Worker { def main(args: Array[String]): Unit = { //构建ActorSystem Actor对象 val host = "192.168.41.41" val port = 9999 val cfgStr = s""" |akka.actor.provider="akka.remote.RemoteActorRefProvider" |akka.remote.netty.tcp.hostname="$host" |akka.remote.netty.tcp.port="$port" """.stripMargin val config = ConfigFactory.parseString(cfgStr) //创建ActorSytem Actor对象 val workerActorSystem = ActorSystem.apply("workerActorSystem", config) //创建Actor对象 val workerActor = workerActorSystem.actorOf(Props(new Worker), "workerActor") } }
-
对象
-
package cn.itcast.common /** * - 样例对象 CheckTimeOut * - 样例类 Registed(msg:String) master反馈给worker信息 * - 样例类 WorkerRegist(workInto:WorkInfo) worker向master注册的信息 * - 类:WorkInfo(包含了worker资源信息(cpu和内存,workerId)) 资源对象 * - 样例对象 HeartBeat worker内部定时任务的标志 * - 样例类 HeartBeatMsg(workerId:String) 向master发送的心跳信息 */ class BaseMsg extends Serializable //样例对象 CheckTimeOut case object CheckTimeOut //样例类 Registed(msg:String) case class Registed(msg: String) extends BaseMsg //类:WorkInfo class WorkInfo(val workerId: String, cpu: Int, memory: Int) extends BaseMsg{ var WORK_STATE: Long = 0 //保存worker状态信息 override def toString: String = { "workerInfo:" + workerId + "\t" + cpu + "\t" + memory } } //样例类 WorkerRegist(workInto:WorkInfo) case class WorkerRegist(workInto: WorkInfo) extends BaseMsg //HeartBeat 样例对象 case object HeartBeat //样例类 HeartBeatMsg(workerId:String)向master发送的心跳信息 case class HeartBeatMsg(workId: String) extends BaseMsg
-
-
-