第五课:彻底精通Scala隐式转换和并发编程及Spark源码阅读

来自 DT大数据梦工厂(哪里写的不合适请指出)


Spark中的并发编程一方面使用了java的元原生线程,另一方面也使用了Scala并发编程的一个框架叫akka,Akka的核心是actor,Scala并发编程的核心也是actor。

Scala中隐式转换基本的思想是,可以手动指定将某种类型的对象或类转换成其它类型的对象或类。

为什么要进行转换???

假设说我们制定好了接口 File。 File里面没有例如dt_spark这个方法,但是实际开发中需要这个方法,这时候就需要把这个方法进行升级变成想要的方法。


implicit convention function

隐式转换函数

implicit def function

如果定义了隐式转换函数,在上下文中会被Scala自动使用, Scala会根据隐式转换函数的签名,在程序运行时,使用隐式转换函数,将接收的参数类型定义的对象,自动升级转换成为隐式转换后的对象,转换为另外一种类型的对象返回,另外一种对象就具有了方法的类型。

RDD 中的隐式转换为什么不需要import就能用??

隐式转换的作用域:原理如果当前上下文没有这个隐式转换,他就会到类的伴生对象中,类的伴生对象里面都是静态的,会找类的伴生对象中有没有import。


隐式转换运行的基本机制:

包括四种类型:1,隐式转换。2,隐式参数。3,隐式类。4,隐士对象。

找implicit的顺序:(1)优先从当前类(有可能是对象的类,也有可能是类本身的类)的伴生对象中找。

(2)把所有的隐式类型都放到object里面,然后import进来,它会从里面找。

(3)从当前的作用域中找隐式的东西。

(4)用到的时候import

找implicit不会消耗资源,也就是循环遍历几个路径罢了。一般都会在伴生对象中写隐式转换。


1:一个隐式转换的例子如下:

//定义一个class

scala> class Person(val name:String)

//定义Engineer 里面有code方法

scala> class Engineer(val name:String,val salary:Double){

      def code = println("Coding....")

      }

//调用p.code 会报错:code不是Person的方法

scala> def toCode(p:Person){

      p.code

      }

<console>:13: error: value code is not a member of Person

       p.code

         ^

//定义一个隐式方法,注意implicit定义的时候一般一定会写返回类型,虽然不是语法强制要求的。

scala> import scala.language._

import scala.language._


scala> implicit def person2Engineer(p:Person): Engineer = {

          new Engineer(p.name,100000)

         }


scala> def toCode(p:Person){

      p.code

      }


scala> toCode(new Person("Scala"))

Coding....

此处就正常工作了,上面我们定义了一个 def toCode(p:Person){p.code} toCode方法接收几个Person类型的实例,这个实例调用了code方法(p.code)而Person本身没有code方法。但是我们想让这个代码工作,(正常情况下肯定出错)。这就必须在程序可见的范围定义隐式转换函数。这个时候Scala会自动使用隐式转换函数,将我们传进来Person类型的实例对象通过person2Engineer这个隐式转换函数,(可以定义很多隐式转换函数,区分不同隐式转换函数的核心就是通过函数的签名,主要就是输入类型)由于Person实例是Person类型的,Scala会发现有implicit开头的函数,所以在出现错误的时候做最后的尝试,发现类型匹配,在implicit def person2Engineer中new了Engineer (p.name,100000)内部是以p.name,和salary ,此时就获得了一个Engineer对象。

此时(就是找Person的code没有找到)实际上 toCode( new Person("Scala”) )  new Person("Scala”)实际返回的是Engineer

实际代码工作PersonEngineer没有任何关系,但在运行中person没有的功能,通过隐式转换,有了这个方法,具体是由Scala帮助实现了。

 

实际生产环境下不能保证自定义的接口是完美的,随着时间可能要有新的方法,这个时候就可以通过隐式转换扩展接口。


隐式转换函数可以随意删除和更改,保证了代码扩展性。


package com.dt.scala.implicits

import java.io.File

import scala.io.Source

/**
  * Created by jiudu on 2016/12/11.
  *
程序从main方法开始执行首先 new File_Implicits了这个类,想使用Fileread方法。
  *
但是实际上File没有read方法,我们定义了一个RicherFile()它参数是File类型的,而RicherFile中有read方法。
  * new File_Implicits
了这个类,它的参数会接收一个文件的路径extends File(path),它是File类型的,想使用它的read方法,但是File本身没有read方法。
  *
这个就在File_Implicits的伴生对象(object File_Implicits{...)中定义了implicit def file2RicherFile(file:File)这样一个隐式转换函数
  *
它的输入类型是File,它可以把这个File升级成为 RicherFile,而RicherFileread方法。
  *
  *
程序执行read时发生了隐式转换
  *
程序执行的时候发现File_Implicits中没有read方法,但是它的伴生对象(全局静态的)的RicherFile中有read方法。
  *
这个例子就是通过隐式转换把file的功能增强了。
  *
  * RDD
中的隐式转换为什么不需要import就能用??
  *
寻找路径要么在上下文,要不在对象或类型的伴生对象里,要不就是手动 import直接导入
  */

class RicherFile(valfile:File) {
 
def read= Source.fromFile(file.getPath()).mkString
}
class File_Implicits(path:String)extends File(path)

object File_Implicits{
 
implicit def file2RicherFile(file:File) =new RicherFile(file)//File -> RicherFile
}

object Implicit_Internals {
 
def main(args: Array[String]):Unit = {
   
val file =new File_Implicits("/Users/bigdata/learn_data/testHiveDriver.txt")
    println(file.read)

  }
}


2:隐式参数

所谓隐式参数,是由运行时上下文实际赋值注入的参数,不需要手动赋值,就可以自动发生效果。这样程序可以自动的帮你赋值,自动的在后台完成很多任务,在上下文中可以建立很多上下文对象,通过隐式参数注入到程序中,这样程序就会按照框架默认的情况去运行。

这样程序就有静态的默认配置,动态运行时默认配置。它具体的工作机制,例如定义了一个参数是隐式参数,实际在运行的时候不需要手动提供这个隐式参数,运行时环境会根据上下文的隐式值(可以是静态写入,也可以是动态的程序运行产生,也可以是配置文件)类型和隐式值本身注射到程序中,让程序正常运行。

Scala会在两个范围查到隐式值,(1)当前的作用域的 val 或 var ,implicit val或 implicit var (2)会到隐式参数类型的伴生对象中去找隐式值。一般情况下都会到隐式参数类型的伴生对象中去找隐式值。


scala> class Level(level:Int)

scala> def toWorker(name:String)(implicit level :Level){

      println(name + ":"+ level)

      }


scala> implicit val level = new Level(8)

level: Level = Level@3e0a9b1d

//调用toWorker

scala> toWorker("Spark")

Spark:$line28.$read$$iw$$iw$$iw$$iw$$iw$Level@3e0a9b1d




scala> class Level(val level:Int)

defined class Level


scala> def toWorker(name:String)(implicit l:Level)=println(name+":"+l.level)//回车

toWorker: (name: String)(implicit l: Level)Unit

//定义隐式值

scala> implicit val level = new Level(8)

level: Level = Level@7d0cc890


scala> toWorker("Spark")

Spark:8

//此处我们只传入了一个参数,并没有传入隐式值level,但是打印了隐式值,隐式值不是手动调用的,是系统自动调用的。


package com.dt.scala.implicits

/**
  *
隐式对象
  *
  * implicit object
impact修饰的object就是隐式对象。
  *
下面有两个方法StringAdd字符串拼接,IntAdd数字相加,他们同时继承一个抽象父类abstract class
  * SubIemplate[T] extends Template[T]
  *
关键代码def sum[T](xs:List[T])(implicit m: SubIemplate[T])sum是范型,第一个参数里面有一个list是范型
  *
第二个参数是隐式参数 隐式参数的类型为SubIemplate
  *
运行的时候没有指定具体参数类型,Scala自动类型推导,发现是Integer,所以List[T] T 就变成Integer
  */
abstract  class Template[T] {
 
def add(x:T,y:T) : T
}
abstract class SubIemplate[T]extends Template[T] {
 
def unit: T
}
object Implicit_Object {

 
def main(args: Array[String]) {
   
implicit object StringAddextends SubIemplate[String] {
     
override def add(x:String, y: String):String = x concat y

     
override def unit:String = ""
   
}

   
implicit object  IntAddextends SubIemplate[Int] {
     
override def add(x:Int, y: Int): Int= x + y
     
override def unit:Int = 0
   
}

   
def sum[T](xs:List[T])(implicitm: SubIemplate[T]):T =
     
if(xs.isEmpty) m.unit
       
//递归调用
     
else m.add(xs.head,sum(xs.tail))//xs.tail指除了第一个元素外剩下的所有元素组成的list

   
println(sum(List(1,2,3,4,5)))
    println(sum(
List("scala","spark","Kafka")))
  }
}



import java.io.File

import scala.io.Source


/**
  *
隐式类
  *
  * import Context_Helper._
把里面所有的静态的东西都导入了。因为是object所以里面都是静态的。
  * 1.addSAP(2)   1
本身没有这个方法,此时 RichInt ,RichInt是系统提供的(例如1 to 10,如果系统提供的隐式转换中没有
  *
此时他就会找上下文,找到Op(x:Int)这个隐式类(名字可以随便写,因为主要看类的签名)发现它接收整数,1正好是整数这个时候就直接调(注意是直接调)用addSAP方法。
  */
object Context_Helper{
  implicit class FileEnhancer(file:File){
    def read= Source.fromFile(file.getPath).mkString
  }
  implicit class Op(x:Int){
    def addSAP(second:Int) = x + second
  }
}
object Implicit_Class {
  def main(args: Array[String]):Unit = {
    import Context_Helper._
    //
   
println(1.addSAP(2))
    println(new File("/Users/bigdata/learn_data/testHiveDriver.txt").read)
  }
}


Scala并发编程


并发编程可高效利用硬件资源

一般,所有高手在进行程序开发时,一定都是并发编程的消息通信模式加上缓存的绝妙的使用。并发编程更好地利用了物理硬件,是高效利用cpu的核心。

scala中提供一个基于Actor的框架,Akka。和java中的Thread类似

大规模的程序应该尽可能减少全局共享变量。


Java中是通过Thread 方法、Synchronize 和 锁(Lock 对象)机制来实现的。Java中Thread极大的利用了多核的潜能,更好的利用了物理硬件。但是它多线程并发工作的原理是基于共享全局变量的加锁机制这就一定不可避免的出现死锁和状态失控,而在做分布式时有一个黄金准则就是一定不要使用共享的全局变量。更不要说加锁机制了。

而在 Scala 中是通过Actor这个trait来实现的。Actor去掉了共享的全局变量,变量是私有的;Scala程序中会有很多的actor,每个actor都有自己的循环器,有自己内部的状态和业务逻辑,不同actor并发编程交互的时候彼此发消息。这就避免了传统的并发编程中出现的死锁等一系列问题。

这里的Actor类似java中Thread,Actor的act方法类似Thread的run方法。


发送消息的语法格式:Actor名 ! 消息内容。

同步消息的语法格式: Actor名 !?  消息内容

接收异步消息 语法格式  val 变量名 = actror名 !! 消息内容 (在未来某个时刻变量或得到actor线程的发送的值)

同步与异步:默认情况下是异步的。

一个ACTOR可以指定给另一个ACTOR发信息。

在 Spark 的源代码中我们也会发现其大量应用,Scala 也正是用这种机制实现了 Master 和 Worker 间的通信。


scala> import scala.actors.Actor

import scala.actors.Actor


scala> class HiActor extends Actor{

     | def act(){

     | while(true){

     | receive {

     | case name:String => println("Hello "+name)

     | }

     | }

     | }

     | }

defined class HiActor


scala> val actor = new HiActor

actor: HiActor = HiActor@37864b77


scala> actor.start()

res0: scala.actors.Actor = HiActor@37864b77


scala> actor ! "Spark"


scala> Hello Spark


用ACTOR类的start()方法来启动线程,再用一种简单的方式(线程类的变量名 ! 模式匹配对象和参数)去发信息,在ACTOR接口中有一个待实现的抽象方法 act(), 在该方法中用一个死循环(while(true))来不停地接受消息,然后进行模式匹配,

实现线程间的通信。这就是 Scala 多线程的核心机制。


case class Basic(name: String, age: Int)

defined class Basic


scala> case class Work(name: String, age: Int)

defined class Work


scala> class basicActor extends Actor{

     | def act(){

     | while(true){

     | receive {

     | case Basic(name, age) => println("basic information" + name + " : " + age

)

     | case Work(name, age) => println("work information" + name + " : " + age)

     | }

     | }

     | }

     | }

defined class basicActor


scala> val a = new basicActor

a: basicActor = basicActor@17e9bc9e


scala> a.start()

res2: scala.actors.Actor = basicActor@17e9bc9e


scala> a ! Basic("scala",10)


scala> basic informationscala : 10


scala> a ! Work("spark",80)


scala> work informationspark : 80


来自 DT大数据梦工厂 课堂笔记



展开阅读全文

没有更多推荐了,返回首页