栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

spark进阶(三):scala基础

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

spark进阶(三):scala基础

spark进阶(三):scala基础

Spark本身就是使用Scala语言开发的,spark和flink的底层通讯都是基于的高并发架构akka开发,然而akka是用scala开发的,Scala与Spark可以实现无缝结合,因此,Scala顺理成章地成为了开发Spark应用的首选语言,大多数spark教程都是基于scala编写,学习一下scala也是很必要的。

这里只对scala在spark中使用的部分进行记录,不涉及一些复杂的scala使用,只保证能正常使用spark即可。

对于scale的话,学过kotlin的同学应该学起来还是很容易的,因为一些写法都很相近;即便没有学过kotlin对于java用户,经常使用java stream的也不会陌生,如果是写过java响应式的话(reactor3, netty)的也应该没有啥学习成本的。

一、变量

Scala 只有两种类型的变量,分别使用关键字val和var进行声明。对于用val声明的变量,在声明时就必须被初始化,而且初始化以后就不能再赋新的值;对于用var声明的变量,是可变的,可以被多次赋值, val定义的变量可以重复定义。

二、输入|输出

从控制台读写数据,可以使用以read为前缀的方法,包括:readInt、readDouble、readByte、readShort、readFloat、readLong、readChar、readBoolean及readLine,分别对应9种基本数据类型,其中,前8种方法没有参数,readLine可以不提供参数,也可以带一个字符串参数的提示。所有这些函数都属于对象scala.io.StdIn的方法:

scala的输出方法有三种:print、println和printf,其中printf不常用。其中print就是打印,println会在打印之后添加换行,printf有format的功能。

Scala提供了字符串插值机制,以方便在字符串字面量中直接嵌入变量的值。为了构造一个插值字符串,只需要在字符串字面量前加一个“s”字符或“f”字符,然后,在字符串中即可以用$插入变量的值,s插值字符串不支持格式化,f插值字符串支持在$变量后再跟格式化参数。

三、读写文件

Scala使用类java.io.PrintWriter实现文本文件的创建与写入。尽管PrintWriter类也提供了printf函数,但是,它不能实现数值类型的格式化写入。为了实现数值类型的格式化写入,可以使用String类的format方法,或者用f插值字符串,和python3.6之后的format方式差不多。

Scala使用类scala.io.Source实现对文件的读取,最常用的方法是getLines方法,它会返回一个包含所有行的迭代器。

四、for循环

与Java的for循环相比,Scala的for循环在语法表示上有较大的区别,同时,for也不是while循环的一个替代者,而是提供了各种容器遍历的强大功能,用法也更灵活。for 循环最简单的用法就是对一个容器的所有元素进行枚举,基本语法结构为:for (变量 <- 表达式) {语句块},其中,“变量<-表达式”被称为“生成器(Generator)”,该处的变量不需要关键字var或val进行声明,其类型为后面的表达式对应的容器中的元素类型,每一次枚举,变量就被容器中的一个新元素所初始化。

for循环不仅仅可以对一个集合进行完全枚举,还可以通过添加过滤条件对某一个子集进行枚举,这些过滤条件被称为“守卫式(Guard)”:

可以通过添加多个生成器实现嵌套的for循环,其中,每个生成器之间用分号隔开,例如:

五、数据结构 1.数组

数组(Array)是一种可变的、可索引的、元素具有相同类型的数据集合,它是各种高级语言中最常用的数据结构。Scala 提供了参数化类型的通用数组类Array[T],其中,T 可以是任意的 Scala类型。Scala数组与Java数组是一一对应的。即Scala的Array[Int]可看作Java的Int[],Array[Double]可看作Java的Double[],Array[String]可看作Java的String[]。

Array提供了函数ofDim来定义二维和三维数组,用法如下:

2.元祖

Scala的元组是对多个不同类型对象的一种简单封装。Scala提供了TupleN类(N的范围为1~22),用于创建一个包含N个元素的元组。构造一个元组的语法很简单的,只需把多个元素用逗号分开并用圆括号包围起来就可以了。

可以使用下划线“_”加上从1开始的索引值,来访问元组的元素:

还可以一次解包,直接赋值:

3.容器

Scala用了三个包来组织容器类,分别是scala.collection、scala.collection.mutable和scala.collection.immutable。从名字即可看出scala.collection.immutable包是指元素不可变的容器;scala.collection.mutable包指的是元素可变的容器;而scala.collection封装了一些可变容器和不可变容器的超类或特质。

在 Iterable下的继承层次包括3个特质,分别是序列(Seq)、映射(Map)和集合(Set),这3种容器最大的区别是其元素的索引方式,序列是按照从0开始的整数进行索引的,映射是按照键值进行索引的,而集合是没有索引的。

4.序列

序列(Sequence)是指元素可以按照特定的顺序访问的容器。在Scala的容器层级中,序列容器的根是collection.Seq特质,是对所有可变和不可变序列的抽象。序列中每个元素均带有一个从0开始计数的固定索引位置。特质Seq具有两个子特质LinearSeq和IndexedSeq,这两个子特质没有添加任何新的方法,只是针对特殊情况对部分方法进行重载,以提供更高效的实现。LinearSeq序列具有高效的head和tail操作,而IndexedSeq序列具有高效的随机存储操作。实现了特质LinearSeq的常用序列有列表(List)和队列(Queue)。实现了特质IndexedSeq的常用序列有可变数组(ArrayBuffer)和向量(Vector)。

IndexedSeq就好比java的ArrayList,LinearSeq就好比java的linkedList。

  • List:immutable , LinearSeq
  • Vector:immutable, IndexedSeq
  • ListBuffer:mutable, LinearSeq
  • ArrayBuffer:mutable,IndexedSeq

由于List是一个特质,因此不能直接用new关键字来创建一个列表:

vector常用操作:

ListBuffer使用:

5.集合

Scala的集合(Set)是不重复元素的容器。相对于列表中的元素是按照索引顺序来组织的,集合中的元素并不会记录元素的插入顺序,而是以“哈希”方法对元素的值进行组织(不可变集在元素很少时会采用其他方式实现),所以,它可以支持快速找到某个元素。集合包括可变集和不可变集,分别位于scala.collection.mutable包和scala.collection.immutable包,缺省情况下创建的是不可变集。

6.映射

映射(Map)是一系列键值对的容器。Scala提供了可变映射和不可变映射,分别定义在包scala.collection.mutable和scala.collection.immutable 里。默认情况下,Scala使用的是不可变映射。操作符“->”是定义二元组的简写方式。

六、面向对象

作为一个运行在JVM上的语言,Scala毫无疑问首先是面向对象的语言。尽管在具体的数据处理部分,函数式编程在Scala中已成为首选方案,但在上层的架构组织上,仍然需要采用面向对象的模型,这对于大型的应用程序尤其必不可少。具体使用方式和java大同小异。

scala中函数式一等公民,对于编写方法这部分和java还是相差不叫大的和kotlin相似。

1.setter和getter
class Counter {
  private var cnt:Int = 0
  // 这里类似于java的setter和getter
  def value: Int = cnt
  def value_=(newValue:Int) {
    if (newValue > 0) cnt = newValue
  }
  def increment(step:Int):Unit={cnt+=step}
}
2.函数定义

在Scala语言中,方法参数前不能加上val或var关键字来限定,所有的方法参数都是不可变类型,相当于隐式地使用了val关键字限定,如果在方法体里面给参数重新赋值,将不能通过编译。

class Counter {
  private var cnt:Int = 0
  // 不需要输入的函数可以不写(),这里函数调用的时候也不能写括号
  def current=cnt
  // 写()的行数调用的时候可以不写()
  def current2()=cnt
  def increment(step:Int):Unit={cnt+=step}
  // 可以不写返回类型,但是必须得写{}
  def increment2(step:Int){cnt+=step}
  // 可以省略掉{}
  def increment3(step:Int)= cnt+=step
  // 可以在赋值的时候设置默认值
  def increment4(step:Int=1)= cnt+=step
}
3.构造器

scala的构造器和kotlin的比较相似,整个类的定义主体就是类的构造器,称为主构造器,所有位于类方法以外的语句都将在构造过程中被执行。可以像定义方法参数一样,在类名之后用圆括号列出主构造器的参数列表。除了主构造器,Scala还可以包含零个或多个辅助构造器(AuxiliaryConstructor)。辅助构造器使用this进行定义,this的返回类型为Unit。

// 可以在创建类的时候直接构造
class Employee(var id:Int=1, var name:String="zhangsan") {
  // 辅助构造器
  def this(id:Int){
    this()
    this.id = id
  }
  // 第二辅助构造器
  def this(name:String){
    this()
    this.name = name
  }
  def getInfo():Unit =println(f"ID: $id, 姓名:$name")
}
4.伴生对象

单例对象包括两种,即伴生对象(Companion Object)和孤立对象(StandaloneObject)。当一个单例对象和它的同名类一起出现时,这时的单例对象被称为这个同名类的“伴生对象”。没有同名类的单例对象,被称为孤立对象。

class Employee(var id:Int=1, var name:String="zhangsan") {
  // 辅助构造器
  def this(id:Int){
    this()
    this.id = id
  }
  // 第二辅助构造器
  def this(name:String){
    this()
    this.name = name
  }
  private val gender = Employee.change()
  def getInfo():Unit =println(f"ID: $id, 姓名:$name, 性别:$gender")
}

object Employee {
  private var gender:String = "male"
  def change(): String ={
    gender = "female"
    gender
  }

  def main(args: Array[String]): Unit = {
    val v1 = new Employee();
    val v2 = new Employee(2, "lisi")
    v1.getInfo()
    v2.getInfo()
  }
}

apply方法:在Scala中,apply方法遵循如下的约定被调用:用括号传递给类实例或对象名一个或多个参数时,Scala会在相应的类或对象中查找方法名为apply且参数列表与传入的参数一致的方法,并用传入的参数来调用该apply方法。

class Employee(var id:Int=1, var name:String="zhangsan") {
  private val gender = Employee.change()
  def getInfo():Unit =println(f"ID: $id, 姓名:$name, 性别:$gender")
  def apply(it: Int)=println(f"apply方法输入为: $it")
}

object Employee {
  private var gender:String = "male"
  def change(): String ={
    gender = "female"
    gender
  }

  def main(args: Array[String]): Unit = {
    val v1 = new Employee();
    val v2 = new Employee(2, "lisi")
    v1(2)
    v2.apply(3)
  }
}

可以直接通过使用伴生对象生成一个类对象:

class Employee(var id:Int=1, var name:String="zhangsan") {
  private val gender = Employee.change()
  def getInfo():Unit =println(f"ID: $id, 姓名:$name, 性别:$gender")
}

object Employee {
  private var gender:String = "male"
  def change(): String ={
    gender = "female"
    gender
  }
  def apply(id:Int, name:String)=new Employee(id, name)
}

object Test{
  def main(args: Array[String]): Unit = {
      // 通过伴生对象生成Employee对象
    val v1 = Employee(3, "wangwu")
    v1.getInfo()
  }
}

unapply方法用于对对象进行解构操作,与apply方法类似,该方法也会被自动调用。可以认为unapply方法是apply方法的反向操作,apply方法接受构造参数变成对象,而unapply方法接受一个对象,从中提取值。unapply方法包含一个类型为伴生类的参数,返回的结果是Option类型

class Employee(var id:Int=1, var name:String="zhangsan") {
  private val gender = Employee.change()
  def getInfo():Unit =println(f"ID: $id, 姓名:$name, 性别:$gender")
}

object Employee {
  private var gender:String = "male"
  def change(): String ={
    gender = "female"
    gender
  }
  def apply(id:Int, name:String)=new Employee(id, name)

  def unapply(arg: Employee): Option[(Int, String, String)] = Some((arg.id, arg.name, arg.gender))
}

object Test{
  def main(args: Array[String]): Unit = {
    // 通过伴生对象生成Employee对象
    val v1 = Employee(3, "wangwu")
    v1.getInfo()
    var Employee(id, name, gender) = v1
    println(f"$id, $name, $gender")
  }
}
5.特质

Java 中提供了接口,允许一个类实现任意数量的接口,相当于达到了多重继承的目的。但是,在Java 8以前,接口的一个缺点是,不能为接口方法提供默认实现,使得该接口的所有类都要重复相同的样板代码来实现接口的功能。为此,Scala从设计之初就对Java接口的概念进行了改进,使用“特质(Trait)”来实现代码的多重复用,它不仅实现了接口的功能,还具备了很多其他的特性。Scala的特质是代码重用的基本单元,可以同时拥有抽象方法和具体方法。Scala中,一个类只能继承自一个超类,却可以混入(Mixin)多个特质,从而重用特质中的方法和字段,实现了多重继承。

object Employee {
  private var gender:String = "male"
  def change(): String ={
    gender = "female"
    gender
  }
  def apply(id:Int, name:String)=new Employee(id, name)
  def unapply(arg: Employee): Option[(Int, String, String)] = Some((arg.id, arg.name, arg.gender))
}

trait Workable{
  var workTime:Int=8
  def salary():Int  // 抽象函数
  // 具体函数
  def isWorking:Boolean={
    println("员工工作中")
    true
  }
}

trait Work extends Workable{
  // 实现函数
  override def salary(): Int = workTime * 1000
  // 重载函数
  override def isWorking: Boolean = {
    println("员工在休息")
    false
  }
}

class Employee(var id:Int=1, var name:String="zhangsan") extends Work {
  private val gender = Employee.change()
  def info():Unit =println(f"ID: $id, 姓名:$name, 性别:$gender")
}

object Test{
  def main(args: Array[String]): Unit = {
    val e1 = Employee(4, "sange")
    e1.isWorking
    println(e1.salary())
  }
}
七、函数式编程

在数学语言里,函数表示的是一种映射关系,其作用是对输入的值进行计算,并返回一个结果,函数内部对外部的全局状态没有任何影响,即在数学语言里,函数是没有副作用的。在编程语言里,我们把这种无副作用的函数称为纯函数,纯函数式编程正是借用了这种纯函数的概念。纯函数的行为表现出与上下文无关的透明性和无副作用性,即函数的调用结果只与输入值有关,而不会受到调用时间和位置的影响,另外,函数的调用也不会改变任何全局对象,这些特性使得多线程的并发应用中最复杂的状态同步问题不复存在。正是这一巨大优势,使得函数式编程在大数据应用和并发需求的驱动下,成为越来越流行的编程范式。

具体来说就是这种无副作用的编程方法,可以很简单的实现高并发,不用去关心并发带来的安全问题。

上面的代码相信学过es6的都比较熟了,就是lambda函数,其实各个语言都有类似的函数,也叫箭头函数(python里不是箭头)。

当函数的每个参数在函数字面量内仅出现一次,可以省略“=>”并用下划线作为参数的占位符来简化函数字面量的表示,第一个下划线代表第一个参数,第二个下划线代表第二个参数,依此类推。

1.curry化

当函数的每个参数在函数字面量内仅出现一次,可以省略“=>”并用下划线作为参数的占位符来简化函数字面量的表示,第一个下划线代表第一个参数,第二个下划线代表第二个参数,依此类推。

可以通过Curry化过程,将一个多参数的普通函数转化为Curry化的函数。

2.常用函数式编程函数

类似java8里的stream写法

object Functional {
  def main(args: Array[String]): Unit = {
    val map = Map(1->"张三", 2->"李四", 3->"王五")
    // foreach
    map.foreach(kv => println(f"${kv._1} -> ${kv._2}"))
    map.foreach{kv => println(f"${kv._1} -> ${kv._2}")}
    map.foreach{case(k,v) => println(f"$k -> $v")}

    // map
    map.map(kv=> f"${kv._1}:${kv._2}").foreach(println(_))

    // flatmap
    map.map(kv=> f"${kv._1}:${kv._2}").flatMap(s=>s.toList).foreach(s => print(f"$s "))

    // filter
    map.filter(kv => kv._1 == 1).foreach(println(_))

    // reduce
    println(map.keys.reduce(_+_+1).toString)

    // fold
    val list = List(1,2,3,4,5)
    println((list foldRight (10))(_-_))
    println((list foldLeft (10))(_-_))

    //partition 通过逻辑判断分组
    println(list.partition(_<4))

    // groupby 通过计算的值作为key分组
    println(list.groupBy(_%2==0))

    // grouped 整体进行分组输入为组数两返回一个iterator
    println(list.grouped(3).next())

    // sliding 活动拆分为对应数量的组
    println(list.sliding(3).next())
  }
}
转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/294887.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号