目标1:(初级)熟练使用scala编写Spark程序
目标2:(中级)动手编写一个简易Spark通信框架
目标3:(高级)为阅读Spark内核源码做准备
2.Scala概述
2.1.什么是Scala
编程语言,java shell javascript
函数式编程:
函数式编程是一种编程思想,主要的思想把运算过程尽量写成一系列的函数调用。
Scala是一种多范式的编程语言,其设计的初衷是要集成面向对象编程和函数式编程的各种特性。
scala之父:Martin Odersky
Scala运行于Java平台(Java虚拟机),并兼容现有的Java程序。
scala是对java的进一步封装,基于java来开发的。
.class
也就是说,scala的代码最终会被编译为字节码文件,并运行在jvm上。
2.2.为什么要学Scala
1.优雅:这是框架设计师第一个要考虑的问题,框架的用户是应用开发程序员,API是否优雅直接影响用户体验。
2.速度快:Scala语言表达能力强,一行代码抵得上Java多行,开发速度快;Scala是静态编译的,所以和JRuby,Groovy比起来速度会快很多。
scala语言风格简洁,也很可能降低了可读性,所以学习及以后开发过程中,都需要有良好的代码规范。
3. 能融合到Hadoop生态圈:Hadoop现在是大数据事实标准,Spark并不是要取代Hadoop,而是要完善Hadoop生态。JVM语言大部分可能会想到Java,但Java做出来的API太丑,或者想实现一个优雅的API太费劲。
4,Spark的开发语言,掌握好scala,就能更轻松的学好spark。
spark: scala java python R
2.3.Spark函数式编程初体验Spark-Shell之WordCount
Q1: 对上述文件内容使用Spark进行单词个数统计?
Q2: 对上述输出结果进行降序 ?
注:上述代码,暂不需要练习
3.Scala开发环境
3.1.安装JDK
因为Scala是运行在JVM平台上的,所以安装Scala之前要安装JDK
使用 # java -version 来验证
确保已安装jdk1.8+
3.2.安装Scala
3.2.1.Windows安装Scala编译器
访问Scala官网http://scala-lang.org/download/2.11.8.html 下载Scala编译器安装包,目前最新版本是2.12.x,但是目前大多数的框架都是用2.11.x编写开发的,Spark2.x使用的就是2.11.x,所以本课程使用2.11.x版本
安装方式一:下载scala-2.11.8.msi后双击打开一路next运行安装。
安装方式二:直接使用免安装版的,解压即可。
安装完成之后,配置环境变量SCALA_HOME和PATH:
可以在cmd窗口下验证: 输入scala -version 查看scala版本
输入scala 可进入scala shell交互模式
输入:q 或:quit退出scala交互命令行。
该交互模式,有一个高大上的名称:REPL
Read Evaluate Print Loop
(读取-求值-打印-循环)
3.2.2.Linux中安装Scala编译器
下载Scala地址https://downloads.lightbend.com/scala/2.11.8/scala-2.11.8.tgz
1,上传并解压Scala到指定目录
# tar -zxvf scala-2.11.8.tgz -C /usr/local/
2,创建一个软连接(可选项)
# ln -s 源文件目录 软连接目录
# ln -s /usr/local/scala-2.11.8 /usr/local/scala
3,配置环境变量,将scala加入到PATH中
# vi /etc/profile
export SCALA_HOME=/usr/local/scala
export PATH=$PATH:$JAVA_HOME/bin:$SCALA_HOME/bin
3.3.Linux下运行第一个scala程序
3.3.1.代码编写:
public class Test{
public static void main(String [] args){
System.out.println(“hello world”);
}}
# vim ScalaTest
object ScalaTest {
def main(args: Array[String]) :Unit={
println("hello scala")
}
}
3.3.2.代码编译:
# scalac ScalaTest
3.3.3.代码运行:
# scala ScalaTest
运行流程(类似于java):
先编译(scalac),再执行(scala)
scala中,不强制要求源文件和类名一致。
3.4.IDEA安装
目前Scala的开发工具主要有两种:Eclipse和IDEA,这两个开发工具都有相应的Scala插件,如果使用Eclipse,直接到官网下载即可
http://scala-ide.org/download/sdk.html 不推荐使用该种方式
IDEA的Scala插件更优秀,有逼格的Spark攻城狮都选择IDEA(只需一次,就会爱上她)
下载地址:http://www.jetbrains.com/idea/download/
下载社区免费版,点击下一步安装即可,安装时如果有网络可以选择在线安装Scala插件。这里我们使用离线安装Scala插件:
1.安装IDEA,点击下一步即可。由于我们离线安装插件,所以点击Skip All and Set Default
2.下载IEDA的scala插件,地址http://plugins.jetbrains.com/?idea_ce
3.5.Scala插件离线安装
3.安装Scala插件:Configure -> Plugins -> Install plugin from disk -> 选择Scala插件 -> OK -> 重启IDEA
选择好插件后,重启IDEA:
3.6.IDEA创建Scala工程
安装完成后, 双击打开IDEA, 创建一个新的项目(Create New Project)
选中左侧的Scala -> IDEA -> Next
输入项目名称 -> 点击Finish完成即可
3.7.IDEA中的第一个scala程序
new 一个 object
定义一个main方法
编写程序
3.8.IDEA常用配置:
Ctrl+Alt + s 进入到settings配置页面。
修改字体:
修改字符集:
如果每次启动IDEA,直接进入到项目页面,没有引导页面,修改配置如下:
修改安装目录的idea.properties文件
# idea.config.path=${user.home}/.IdeaIC/config
idea.config.path=d:/profiles/develop/IDEACache/config
#---------------------------------------------------------------------
# Uncomment this option if you want to customize path to IDE system folder. Make sure you're using forward slashes.
#---------------------------------------------------------------------
# idea.system.path=${user.home}/.IdeaIC/system
idea.system.path=d:/profiles/develop/IDEACache/system
4.Scala基础
4.1.常用类型
Scala和java一样,
AnyVal
有7种数值类型:Byte、Char、Short、Int、Long、Float和Double(没有基本类型和包装类型的区分)
2种非数值类型: Boolean 和 Unit
注意:Unit表示无值,相当于java中的void。用作不返回任何结果或者结果为空的类型。Unit类型只有一个实例值,写成()。(小括号)
String是属于引用类型
AnyRef
4.2.声明变量
String a=””
val a:String=””
定义变量使用var 或者 val关键字
语法: var|val 变量名称 (: 数据类型) = 变量值
使用val定义的变量是不可变的,相当于java中用final修饰的变量
使用var定义的变量是可变的,推荐使用val
Scala编译器会自动推断变量的类型,必要的时候可以指定类型
lazy val 变量名
lazy关键字修饰的变量,是一个惰性变量,实现延迟加载(懒加载),在使用的时候才会加载。
lazy关键字不能修饰 var类型的变量
object VariableTest {
def main(args: Array[String]) {
// 使用val定义的变量值是不可变的,相当于java里用final修饰的变量
//变量名在前,类型在后
val name: String = “nvshen”
// 使用var定义的变量是可变的,在Scala中鼓励使用val
var age = 18
//Scala编译器会自动推断变量的类型,可以省略变量类型
val str = "world"
// 声明多个变量
var age,fv = 18
var str: String = _
}
}
可以同时声明多个变量,可以使用通配符声明变量:
java中的通配符是*,scala中的通配符是_
定义一个变量,必须赋予初始值,如果没有初始值,可以使用_占位符代替,但是变量必须指定类型。而且占位符变量不能定义在main方法内部。
4.3.条件表达式
表达式都是有返回值的。
条件表达式的值可以赋值给一个变量
支持混合类型的表达式。
Scala的条件表达式比较简洁,例如:
object ConditionTest {
def main(args: Array[String]) {
val x = 1
//判断x的值,将结果赋给y
val y = if (x > 0) x else -1
//打印y的值
println(y)
//如果缺失else,相当于if (x > 2) 1 else ()
val m = if (x > 2) 1
println(m)
//在scala中每个表达式都有返回值,scala中有个Unit类,写做(),相当于Java中的void
val n = if (x > 2) 1 else ()
println(n)
//支持混合类型表达式
val z = if (x > 1) 1 else "error"
//打印z的值
println(z)
混合类型会返回父类类型。
//if和else if
val k = if (x < 0) 0 else if (x >= 1) 1 else -1
println(k)
}
}
混合类型中,返回值类型是多个分支的返回值类型的父类(超类)
每一个条件语句,都有返回值,默认返回值是最后一行的值,如果没有内容或者内容为空,那么返回值类型就是Unit,值是()。
多条件分支中,缺少了某一个分支,默认该分支的返回值为Unit。
条件表达式,可以使用{},写在一行时,或者只有一行内容时,可以省略{}。
if(x>2) 3 else 5
1,表达式都是有返回值的
2,返回值的类型,分支类型不同,返回值类型是各分支的返回值类型的父类型。如果缺少了else分支,那么该分支的返回值类型是Unit,值是()
(Int和Unit,返回值类型是AnyVal,Int和String,返回值类型是Any)
3,返回什么值? 是由每一个分支的最后一行返回值决定的
4,什么时候可以省略分支的大括号:分支的结构只有一行,
4.4.块表达式
{} 称为块表达式,块表达式中可以包含一系列的表达式,最后一个表达式的值就是块的值。
object BlockExpressionTest {
def main(args: Array[String]) {
val x = 0
//在scala中{}中可包含一系列表达式,块中最后一个表达式的值就是块的值
//下面就是一个块表达式
val result = {
if (x < 0){
-1
} else if(x >= 1) {
1
} else {
"error"
}
}
// result的值就是块表达式的结果
println(result)
}
}
4.5.循环
在scala中有for循环和while循环, for循环最常用
4.5.1.for循环
语法结构:for (i <- 表达式/数组/集合)
java for循环方式:
// for(i=1;i<10;i++) // 传统for循环
// for(Int I :arr) // 增强for循环
object ForTest {
def main(args: Array[String]) {
//for(i <- 数组)
val arr = Array("a", "b", "c")
// 遍历打印数组中的每个元素
for (i <- arr) // 类似Java中的增强for
println(i)
// 通过角标获取数组中的元素
val index = Array(0,1,2)
// 遍历打印数组中的每个元素
for (i <- index) // 类似Java中的传统for
println(arr(i)) // 获取元素的方式是(),java中是[]
//for(i <- 表达式),表达式1 to 10返回一个Range(区间)
//每次循环将区间中的一个值赋给i
for (i <- 1 to 6)
println(i)
println(arr(i)) // 报错,如果不加{},只会把for后面的一行当做循环的内容。
for (i <- 1 to 6){
println(i)
println(arr(i))
}
for(i <- 1 until 6) { // 0 until 6 => 会生成一个范围集合Range(0,1,2,3,4,5)
println(array(i))
}
// 打印数组中的偶数元素
// 在for循环中,通过添加守卫来实现输出满足特定条件的数据
for(e <- arr if e % 2 == 0) { // for表达式中可以增加守卫
println(e)
}
//高级for循环
//每个生成器都可以带一个条件
for(i <- 1 to 3; j <- 1 to 3 if i != j){
print((10 * i + j) + " ")
}
//for推导式:如果for循环的循环体以yield开始,则该循环会构建出一个集合
//每次迭代生成集合中的一个值
val v = for (i <- 1 to 10) yield i * 10
println(v)
}
}
两个生成器: to until
1 to 10 生成 Range(1, 2, 3, 4, 5, 6, 7, 8, 9, 10) 左闭右闭区间
1 until 10 生成 Range(1, 2, 3, 4, 5, 6, 7, 8, 9) 左闭右开区间
for循环总结:
关键标识: <-
可以使用增强for循环,直接操作元素
使用to和until,可以使用下标来操作集合
for可以带守卫,可以用yield推导式
嵌套的for循环
4.5.2.while循环
Scala 的 while 循环和其它语言如 Java 功能一样,它含有一个条件,和一个循环体,只有条件满足,就一直执行循环体的代码。
语法结构:while(condition){ 循环体内容 }
var i = 0
while(i<5) {
println(i)
i += 1 // i = i + 1
}
Scala 也有 do-while 循环,它和 while 循环类似,只是检查条件是否满足在循环体执行之后检查。
i = 0
// while 直接判断
while(i>0 && i<=5) {
println(i)
i += 1
}
i = 0
// do while 先执行一次循环,再进行判断
do{
println(i)
i += 1
}while(i>0 && i<=5)
循环也有返回值,只不过都是Unit
val res = while(i>0 && i<=5) {
println(i)
i += 1
}
println(s"res = $res")
插值法
4.6.函数式编程再体验:
map:对集合或者数组中的每一个元素进行操作,该方法接收一个函数,具体的业务逻辑是自己定义的。
filter: 过滤,过滤出满足条件的元素。
4.7.调用方法(运算符重载为方法)
Scala中的+ - * / %等操作符的作用与Java一样。只是有一点特别的:
这些操作符实际上是方法。操作符被重载为方法。
例如:
a + b
是如下方法调用的简写:
a.+(b)
a 方法 b可以写成 a.方法(b)
5.方法和函数
方法:一段业务逻辑的综合。
5.1.定义方法
java中的方法:
public int add(int a,int b){
return a + b;
def methodName ([list of parameters]) : [return type] = {}
/**
* 方法的定义及调用
*
* 定义方法的格式为:
* def methodName ([list of parameters]) : [return type] = {}
* 如果不使用等号和方法体,则隐式声明抽象(abstract)方法。
*/
object ScalaMethod extends App{
// 定义个sum方法, 该方法有2个参数, 参数类型为整型, 方法的返回值为整型
def sum(a:Int, b: Int): Int = {
a + b
}
// 定义有可变参数的方法,
def sumAll(b: Int*): Int = {
var v = 0
for (i<- b){
v += i
}
v // 返回值
}
// 调用
val result1 = sum(1, 5)
println(result1)
println(sumAll(1,11,13))
// 该方法没有任何参数, 也没有返回值
def sayHello1 = println("Say BB1")
def sayHello2() = println("Say BB2")
sayHello1 // 如果方法没有() 调用时不能加()
sayHello2 // 可是省略(), 也可以不省略
}
总结:
调用空参方法时,可以省略参数列表的(),但是如果定义空参方法时没有添加参数列表(),则在调用时,不能加()。
方法不能做为最终的表达式单独存在,必须显示调用。(无参的方法调用时,可以省略())
方法的返回值类型,可以省略,编译器会自动推断出来,但是两种情况下必须指定
(1,递归调用的方法,2,有return返回值的方法,必须显示声明返回值类型。)
对于递归方法,必须指定返回类型
对于有return 关键字的,必须指定返回类型
def rec(n:Int):Int= if (n==0) 0 else n*rec(n-1)
def meth1(x:Int,y:Double):Double = return x*y
方法总结:
1,使用def关键字来定义方法
2,方法的参数列表,是自定义的,可以为空,可以为可变参数
3,方法的返回值类型,可以不写,编译器会自动推导出来,但是递归调用的方法和有return关键字修饰的方法,必须要有返回值类型。
4,方法必须被显示调用,不能作为最终的表达式存在。空参方法定义时,省略了(),属于空参方法调用。
5,返回值类型是什么?最后一行代码的返回值决定的。
5.2.定义函数
和方法类似,基本能实现相同的功能
val| var 函数名称=(函数的参数列表) => 函数体
函数可以作为最终的表达式存在,返回的内容就是函数的签名
签名(函数的名称,函数的参数,函数的返回值类型)
这种定义方式不需要指定返回值类型,编译器会自动推断
第二种定义方式:
复杂全面的定义
val | var 函数名称:(输入参数类型)=> 返回值类型 = (参数的引用)=> 函数体
定义一个无参的函数
不同于方法,没有参数的函数定义,也必须加()
val f2:()=>Unit =() => println(123) val f2 =() => println(123) 返回值类型为Unit
val f2:()=>Int =() => 123 val f2=() => 123 返回值类型为Int
5.3.方法和函数的区别
在函数式编程语言中,函数是“头等公民”,它可以像任何其他数据类型一样被传递和操作
函数和变量,类,对象,一个级别,方法,要归属于类或者对象
区别和联系:
1,方法用def关键字定义,函数的标识 =>
2,方法不能作为最终的表达式存在,但是函数可以,返回函数的签名信息
3,方法和函数调用的时候都需要显示的传入参数
4,函数可以作为方法的参数,和返回值类型。
案例:首先定义一个方法,再定义一个函数,然后将函数传递到方法里面
object MethodAndFunctionTest {
//定义一个方法
//方法m2参数要求是一个函数,函数的参数必须是两个Int类型
//返回值类型也是Int类型
def m1(f: (Int, Int) => Int) : Int = {
f(2, 6)
}
//定义一个函数f1,参数是两个Int类型,返回值是一个Int类型
val f1 = (x: Int, y: Int) => x + y
//再定义一个函数f2
val f2 = (m: Int, n: Int) => m * n
//main方法
def main(args: Array[String]) {
//调用m1方法,并传入f1函数
val r1 = m1(f1)
println(r1)
//调用m1方法,并传入f2函数
val r2 = m1(f2)
println(r2)
}
}
// 定义一个普通方法
def max(x:Int,y:Int) = if(x>y)x else y
// 定义一个方法,参数是一个函数,参数只需要函数签名,在调用的时候具体再传入函数体
def max1(f:(Int,Int)=>Int) = f(20,10)
def max2(f:(Int,Int) => Int,x:Int,y:Int)= f(x,y)
// 定义一个方法,方法返回值是函数
def max3()= (x:Int,y:Int)=> if (x>y) x else y
def main(args: Array[String]): Unit = {
println(max(10,20))
println(max1((x:Int,y:Int)=>if(x>y) x else y))
println(max2((x:Int,y:Int)=>if(x>y) x else y,10,20))
println(max3()(10,20))
}
5.4.将方法转换成函数(神奇的下划线)
找到一份满意的工作:
技术 + 表达能力 + 运气
笔试,
面试:最重要的环节 讲出来
6.元组Tuple
与数组或列表不同,元组可以容纳不同类型的对象,但它们也是不可变的。
元组是不同类型元素的集合
6.1.创建元组
定义元组时,使用小括号将多个元素括起来,元素之间使用逗号分隔,元素的类型可以不同,元素的个数任意多个(不超过22个)
注意:元组没有可变和不可变之分,都是不可变的。
val t = (12.3, 1000, "spark")
val t4 = new Tuple4(1,2.0,"",3) // 必须4个元素
6.2.获取元组中的值
获取元组的值使用下标获取,但是元组的下标时从1开始的,而不是0
6.3.将对偶的集合转换成映射
6.4.拉链操作
zip命令可以将多个值绑定在一起,生成元组
val name=Array("xx1","xx2","xx3",”xx4”)
val values=Array(1,2,3)
name.zip(values)
多个zip # name zip values zip values.map(_*10)
注意:如果两个数组的元素个数不一致,拉链操作后生成的数组的长度为较小的那个数组的元素个数
6.5.元素交换
可以使用 Tuple.swap 方法来交换对偶元组的元素。
// 定义元组
var t = (1, "hello", true)
// 或者
val tuple3 = new Tuple3(1, "hello", true)
// 访问tuple中的元素
println(t._2) // 访问元组总的第二个元素
// 迭代元组
t.productIterator.foreach(println)
// 对偶元组
val tuple2 = (1, 3)
// 交换元组的元素位置, tuple2没有变化, 生成了新的元组
val swap = tuple2.swap
元组类型Tuple1,Tuple2,Tuple3等等。目前在Scala中只能有22个上限,如果需要更多个元素,那么可以使用集合而不是元组。
7.案例wordCount
// 原始数据
val arr = Array[String]("hello tom hello jim","hello spark hello jim tom")
// 一行完全可以实现,但是不推荐
// arr.map(x=>x.split(" ")).flatten.map(x=>(x,1)).groupBy(x=>x._1).map(x=>(x._1,x._2.length)).toList.sortBy(x=> -x._2)
// .foreach(x=>println(x))
// 目标: 做wordcount
// 数据切分
// val lines: Array[Array[String]] = arr.map(x=>x.split(" "))
// 数据压平
// val flatLines: Array[String] = lines.flatten
// 有一个替代的方法,flatMap 先map 再flatten
val flatLines = arr.flatMap(x=>x.split(" "))
// 和1 组装起来
val wordsWithOne: Array[(String, Int)] = flatLines.map(w=>(w,1))
// 按照单词分组 就是元组的第一个元素
val grouped: Map[String, Array[(String, Int)]] = wordsWithOne.groupBy(t=>t._1)
// 统计单词出现的次数 正常情况下是求和,但是这里是一个特例,所以可以使用长度来判断次数
val result: Map[String, Int] = grouped.map(t=>(t._1,t._2.length))
// 排序
// map不能直接排序,那么就转变成list,然后调用List集合上的sortBy方法
val sortedResult: List[(String, Int)] = result.toList.sortBy(t=> -t._2)
// sortedResult.foreach(x=>println(x))
sortedResult.foreach(println)
scala复习
最新推荐文章于 2022-06-29 10:23:16 发布
![](https://img-home.csdnimg.cn/images/20240711042549.png)