目标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复习
猜你喜欢
转载自blog.csdn.net/a331685690/article/details/80618124
今日推荐
周排行