第五课 Scala隐式转换和并发编程及Spark源码阅读
来源:互联网 发布:美国陆军 知乎 编辑:程序博客网 时间:2024/06/06 02:53
一:隐式转换
1) 隐式转换案例
object ABCDMain extends App {
class B
class C {
override def toString() = "I am C";
def printC(c: C) = println(c);
}
class D
implicit def B2C(b: B) = {
println("B2C")
new C
}
implicit def D2C(d: D) = {
println("D2C")
new C
}
new D().printC(new B)
}
先调用 D2C转换函数将new D()转换成C类,然后调用C类的printC方法;但发现传入的参数类型是B类,于是搜索当前范围有无合适的转换函数,发现B2C转换函数符合要求
2) 隐式操作规则
隐式定义是指编译器为了修正类型错误而允许插入到程序中的定义。例如,如果x + y不能通过类型检查,那么编译器就可能把它修改为convert(x) + y ,这里的convert实质可用的隐式转换。
列如:scala> val a = 4
a: Int = 4
scala> val b = "3"
b: String = 3
scala> def sum(x: Int, y: Int) = x + y
sum: (x: Int, y: Int)Int
scala> sum(a, b)
<console>:13: error: type mismatch;
found : String
required: Int
sum(a, b)
^
此时类型不匹配,我们添加一个函数:
scala> implicit def str2Int(str: String) = Integer.valueOf(str).toInt
warning: there were 1 feature warning(s); re-run with -feature for details
str2Int: (str: String)Int
再次执行:scala> sum(a, b)
res4: Int = 7
此时b 被转成了我们期待的Int 类型,对于函数sum来说 convert就是str2Int,此时如果还有其他的函数也有类型转换的需求就可以简化代码。
隐式转换由下面的规则掌控:
a) 标记规则:只有标记为implicit的定义才是可用的。implicit关键字用来标记编译器可以用于隐式转换,可以使用它标记任何变量、函数,对象、参数。
Spark源码中:
隐式对象:
implicit val keyConverter = keyWritableFactory.convert
implicit val valueConverter = valueWritableFactory.convert
new SequenceFileRDDFunctions(rdd,
keyWritableFactory.writableClass(kt), valueWritableFactory.writableClass(vt))
implicit def rddToSequenceFileRDDFunctions[K, V](rdd: RDD[(K, V)])
(implicit kt: ClassTag[K], vt: ClassTag[V],
keyWritableFactory: WritableFactory[K],
valueWritableFactory: WritableFactory[V])
: SequenceFileRDDFunctions[K, V] = {
implicit val keyConverter = keyWritableFactory.convert
implicit val valueConverter = valueWritableFactory.convert
new SequenceFileRDDFunctions(rdd,
keyWritableFactory.writableClass(kt), valueWritableFactory.writableClass(vt))
}
这里创建SequenceFileRDD时使用了隐式的key,value对象来做转换
隐式方法:
implicit defrddToPairRDDFunctions[K,V](rdd:RDD[(K,V)])
(implicitkt:ClassTag[K],vt:ClassTag[V],ord:Ordering[K]= null):PairRDDFunctions[K,V]= {
newPairRDDFunctions(rdd)
}
RDD的伴生对象中提供的implicit 方法
Word count程序中 sc.textFile(“/usr/local/word.txt”).flatMap(_.split(“\\s+”).map((_,1)).reduceByKey(_+_).collect
就调用了这implicit 方法
隐式参数:
def hadoopFile[K,V,F <:InputFormat[K,V]](path:String)
(implicitkm:ClassTag[K],vm:ClassTag[V],fm:ClassTag[F]):RDD[(K,V)]= withScope{
hadoopFile[K,V,F](path,defaultMinPartitions)
}
这里我们用sc.textFile读取hadoop上的文件是,hadoopFile使用隐式参数km,vm保存了输入hadoop输入的key、value类型信息。
b) 作用域规则:插入的隐式转换必须以单一的标示符形式处于作用域中,或者与转换的源或目标类型关联在一起,scala编译器仅仅考虑处于作用域之内的隐式转换。
列如上面的word count程序sc.textFile方法调用后会产生HadoopRDD而hadoopRDD中并没有flatMap方法,而hadoopRDD的父类中的RDD的伴生对象object RDD中有flatMap方法此时编译器就会调用
object RDD中的:
def flatMap[U:ClassTag](f:T =>TraversableOnce[U]):RDD[U]= withScope{
valcleanF=sc.clean(f)
newMapPartitionsRDD[U,T](this,(context,pid,iter)=> iter.flatMap(cleanF))
}方法来创建MapPartitionsRDD
这样每次RDD转换时,编译器都会从object RDD查找此RDD没有定义的方法来实现关联,这样就不需要在程序中单独引用转换了。
c) 无歧义规则:隐式转换只有不存在其他可以插入的转换前提下才能插入,此时编译器会报错
以之前隐式操作规则 那小节的案例说明:
我们继续定义一个strt 到Int 的转换:
scala> implicit def str2Int2(str: String) = Integer.valueOf(str).toInt
warning: there were 1 feature warning(s); re-run with -feature for details
tr2Int2: (str: String)Int
我们再次调用 sum方法;
sum(a, b)
<console>:17: error: type mismatch;
found : String
required: Int
Note that implicit conversions are not applicable because they are ambiguous:
both method str2Int of type (str: String)Int
and method str2Int2 of type (str: String)Int
are possible conversion functions from String to Int
sum(a, b)
^
此时编译器拒绝了我们定义的隐式转换。
d) 命名隐式转换:隐式转换可以任意命名,因为编译器是通过方法的参数信息来推断应该来调用哪个方法,而不是方法的名称。最佳实践:方法名称就能看出此方法是做了是么转换:
implicit defrddToPairRDDFunctions[K,V](rdd:RDD[(K,V)])
(implicitkt:ClassTag[K],vt:ClassTag[V],ord:Ordering[K]= null):PairRDDFunctions[K,V]= {
newPairRDDFunctions(rdd)
}
此方法名称rddToPairRDDFunctions表明是将RDD转换成PairRDDFunctions
3) 隐式参数:
由运行时上下文实际赋值注入的参数,不需要手动赋值,自动完成。一般到隐式参数类型的伴生对象中去找隐式值。
scala> class Level(val level : Int)
defined class Level
scala> implicit val lelvel = new Level(9)
lelvel: Level = Level@12028586
scala> def toWorker(name: String)(implicit level: Level) {println(name + ":" + level.level)}
toWorker: (name: String)(implicit level: Level)Unit
scala> toWorker("wjl")
wjl:9
注意val 本身也被标记为implicit,如果不是的话编译器就不能使用它。Implicit关键字是作用于全体参数列表,而不是单独的参数。
scala> val a = 4
a: Int = 4
scala> implicit val b = "s"
b: String = s
scala> implicit val c = 56
c: Int = 56
scala> def sum(x: Int)(implicit y: Int, z: String) = x + y + z
sum: (x: Int)(implicit y: Int, implicit z: String)String
scala> sum(4)
res4: String = 60s
从REPL的显示我们看到z也被加上了implicit关键字。
4) 隐式对象:
scala> abstract class Template[T] {
| def add(x: T, y: T): T
| }
defined class Template
scala> abstract class SubTemplate[T] extends Template[T] {
| def unit: T
| }
defined class SubTemplate
scala> implicit object StringAdd extends SubTemplate[String] {
| override def add(x: String, y: String) = x concat y
| override def unit: String = ""
| }
defined module StringAdd
scala> implicit object IntAdd extends SubTemplate[Int] {
| override def add(x: Int, y: Int) = x + y
| override def unit: Int = 0
| }
defined module IntAdd
scala> def sum[T](xs: List[T])(implicit m: SubTemplate[T]): T = {
| if (xs.isEmpty) m.unit
| else m.add(xs.head, sum(xs.tail))}
sum: [T](xs: List[T])(implicit m: SubTemplate[T])T
scala> println(sum(List(1, 2, 3, 4, 5)))
15
scala> println(sum(List("Scala", "Spark", "Kafka")))
ScalaSparkKafka
这里利用了scala的类型推断功能,传入不同的类型调用不同的函数,传入String类型的List时T被替换成String,传入Int类型时T被替换成Int
5) 隐式类:
scala> 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
| }
| }
defined module Context_Helper
scala> import Context_Helper._
import Context_Helper._
scala> println(1.addSAP(2))
3
这里1 没有 addSAP 方法,首先回到RichInt里面去找,然后到运行的上下文中去找,然后到Context_Helper.中直接调用addSAP方法,这里并没有返回一个对象而是直接调用。原因是这里隐式类Op在构造的时候,传入了1这个参数,然后调用了Op对象的addSAP方法。
总结:implicit 1.从当前类的伴生对象中找2. 把所有的隐式转换放在一个object中,然后倒入3.从当前作用域中的隐式转换中找4 在用到隐式转换的时候定义或者倒入
最佳实践:在伴生对象中定义隐式转换
object RDD {
// The following implicit functions were in SparkContext before 1.3 and users had to
// `import SparkContext._` to enable them. Now we move them here to make the compiler find
// them automatically. However, we still keep the old functions in SparkContext for backward
// compatibility and forward to the following functions directly.
implicit defrddToPairRDDFunctions[K,V](rdd:RDD[(K,V)])
(implicitkt:ClassTag[K],vt:ClassTag[V],ord:Ordering[K]= null):PairRDDFunctions[K,V]= {
newPairRDDFunctions(rdd)
}
implicit defrddToAsyncRDDActions[T:ClassTag](rdd:RDD[T]):AsyncRDDActions[T]= {
newAsyncRDDActions(rdd)
}
implicit defrddToSequenceFileRDDFunctions[K,V](rdd:RDD[(K,V)])
(implicitkt:ClassTag[K],vt:ClassTag[V],
keyWritableFactory:WritableFactory[K],
valueWritableFactory:WritableFactory[V])
:SequenceFileRDDFunctions[K,V]= {
implicit valkeyConverter =keyWritableFactory.convert
implicit valvalueConverter =valueWritableFactory.convert
newSequenceFileRDDFunctions(rdd,
keyWritableFactory.writableClass(kt),valueWritableFactory.writableClass(vt))
}
implicit defrddToOrderedRDDFunctions[K: Ordering: ClassTag,V:ClassTag](rdd:RDD[(K,V)])
:OrderedRDDFunctions[K,V,(K,V)]= {
newOrderedRDDFunctions[K,V,(K,V)](rdd)
}
implicit defdoubleRDDToDoubleRDDFunctions(rdd:RDD[Double]):DoubleRDDFunctions ={
newDoubleRDDFunctions(rdd)
}
implicit defnumericRDDToDoubleRDDFunctions[T](rdd:RDD[T])(implicitnum:Numeric[T])
:DoubleRDDFunctions ={
newDoubleRDDFunctions(rdd.map(x=> num.toDouble(x)))
}
}
二:并发编程
并发编程在任意的程序开发过程中都是至关重要的,并发编程的消息通信模式加上缓存的绝妙使用。并发编程至于高效的利用CPU和硬件资源是至关重要的,是高效程序中的核心中的核心。Scala中提供了actor来做并发编程,语言级别支持,scala跟java中的Thread类似。Java中的Thread是java中极大的成功也是极大的失败,成功:更好地利用了多核并发的潜能,物理硬件。失败:java中的并发是基于共享全局变量的加锁机制,这一定会不可避免地带来死锁,状态失控。分布式系统黄金准则:一定不要有全局共享的变量,更不要有加锁。Scala中的actor尽可能地减少死锁和状态共享,每个actor内部都有自己的循环器和状态。Actor框架akka。
1)实战:
scala> import scala.actors.Actor
import scala.actors.Actor
scala> class HiActor extends Actor {
| def act() {
| while(true){
| receive {
| case name :String => println(name)
| }}}
| }
defined class HiActor
Actor是在另外一个线程中启动。
2)最佳实践receive接收case class :
Spark 中Master中的部分代码:
override defreceive:PartialFunction[Any, Unit]= {
caseElectedLeader =>{
val(storedApps,storedDrivers,storedWorkers)= persistenceEngine.readPersistedData(rpcEnv)
state= if (storedApps.isEmpty&& storedDrivers.isEmpty&& storedWorkers.isEmpty) {
RecoveryState.ALIVE
}else {
RecoveryState.RECOVERING
}
logInfo("I have been elected leader! New state: "+ state)
if(state== RecoveryState.RECOVERING) {
beginRecovery(storedApps,storedDrivers,storedWorkers)
recoveryCompletionTask= forwardMessageThread.schedule(newRunnable {
override defrun():Unit = Utils.tryLogNonFatalError{
self.send(CompleteRecovery)
}
},WORKER_TIMEOUT_MS,TimeUnit.MILLISECONDS)
}
}
caseCompleteRecovery =>completeRecovery()
caseRevokedLeadership =>{
logError("Leadership has been revoked -- master shutting down.")
System.exit(0)
}
动手案例:
scala> case class Basic(name: String, age :Int)
defined class Basic
scala> case class Worker(name: String, age :Int)
defined class Worker
scala> class BasicActor extends Actor{
| def act() {
| while(true){
| receive{
| case Basic(name, age) => println("basic info: " + name + ":" + age)
| case Worker(name, age) => println("worker info:" + name + ": " + age)
| }
| }
| }
| }
defined class BasicActor
scala> val ac = new BasicActor
ac: BasicActor = BasicActor@3d3b272a
scala> ac start
warning: there were 1 feature warning(s); re-run with -feature for details
basic info: wj:45
res16: scala.actors.Actor = BasicActor@3d3b272a
scala> ac ! new Basic("wj", 45)
basic info: wj:45
scala> ac ! new Worker("wj", 75)
worker info:wj: 75
备注:receive那里是一个偏函数,使用了模式匹配
!是异步发送消息,发完之后不等待
!? 是同步发送消息,发送后会等待回复
!!是异步消息处理返回值,未来的某个时间获得结果,不会阻塞
总结:因为 actor 编程需要与 “传统” 对象编程不同的风格,所以在使用 actor 时要记住几点。
首先,actor 的主要能力来源于消息传递风格,而不采用阻塞-调用风格,这是它的主要特点。(有意思的是,也有使用消息传递作为核心机制的面向对象语言。最知名的两个例子是Objective-C 和Smalltalk,还有ThoughtWorker 的Ola Bini 新创建的Ioke)。如果创建直接或间接扩展Actor 的类,那么要确保对对象的所有调用都通过消息传递进行。
第二,因为可以在任何时候交付消息,而且更重要的是,在发送和接收之间可能有相当长的延迟,所以一定要确保消息携带正确地处理它们所需的所有状态。这种方式会:
让代码更容易理解(因为消息携带处理所需的所有状态)。
减少 actor 访问某些地方的共享状态的可能性,从而减少发生死锁或其他并发性问题的机会。
第三,actor 应该不会阻塞,您从前面的内容应该能够看出这一点。从本质上说,阻塞是导致死锁的原因;代码可能产生的阻塞越少,发生死锁的可能性就越低。
很有意思的是,如果您熟悉 Java Message Service (JMS) API,就会发现我给出的这些建议在很大程度上也适用于JMS — 毕竟,actor消息传递风格只是在实体之间传递消息,JMS消息传递也是在实体之间传递消息。它们的差异在于,JMS消息往往比较大,在层和进程级别上操作;而 actor消息往往比较小,在对象和线程级别上操作。如果您掌握了 JMS,actor也不难掌握。
- 第五课 Scala隐式转换和并发编程及Spark源码阅读
- 第五课:彻底精通Scala隐式转换和并发编程及Spark源码阅读
- 3000门徒内部训练绝密视频(泄密版)第5课:彻底精通Scala隐式转换和并发编程及Spark源码阅读
- 第17课:Scala并发编程实战及Spark源码阅读
- 第3课:Scala函数式编程彻底精通及spark源码阅读
- 第3课:Scala函数式编程彻底精通及Spark源码阅读
- 第三课 Scala函数式编程彻底精通及Spark源码阅读
- day5:Scala隐式转换和并发编程
- scala隐式转换 及并发
- 3000门徒内部训练绝密视频(泄密版)第3课:Scala中函数式编程彻底精通及Spark源码阅读
- Scala面向对象彻底精通及Spark源码阅读
- Scala面向对象彻底精通及Spark源码阅读
- Scala面向对象彻底精通及Spark源码阅读
- 第5课:零基础实战Scala函数式编程及Spark源码解析
- 第14课Scala集合上的函数式编程实战及Spark源码鉴赏
- Scala隐式转化和并发编程
- 大数据spark蘑菇云行动前传第5课:零基础彻底实战Scala函数式编程及Spark源码解析
- 大数据Spark “蘑菇云”行动前传第5课:零基础实战Scala函数式编程及Spark源码解析
- 转载 CATransform3D 矩阵 m11–m44每个的含义
- MVC 实现数据导入Excel,并在客户端下载。
- Android性能优化典范(三)
- mybatis generator with java IDEA MAVEN
- 实现通讯录分组排序的一个方法
- 第五课 Scala隐式转换和并发编程及Spark源码阅读
- 树形菜单
- css圆角技巧
- android 自定义跑马灯效果,自由控制跑马灯 你未必知道这样用
- Xcode编译警告Assigning to 'id<XXXDelegat> ——Nullable' from incompatible type 'XXXView *const_strong'
- python爬虫helloworld程序
- Java基础--封装、继承、多态
- Excel行列转换
- Fragment 之间的传值