scala
apply方法
apply方法既不是主构造函数,也不是辅助构造函数,他只是一个语法糖方便创建对象。
-
没有在伴随对象实现apply的方法,必须用new关键字创建对象
-
apply方法必须实现在伴随对象里面
class Person(name: String, age: Int) {
def this() {
this("chen", 10)
}
val a = 10
println("语句块")
def func(): Unit = {
println("方法")
}
def apply(): Person = new Person()
def apply(name: String, age: Int): Person = new Person(name, age)
}
object Person {
def apply(): Person = new Person()
def apply(name: String, age: Int): Person = new Person(name, age)
def main(args: Array[String]): Unit = {
val person = Person()
}
}
继承
//父类
class Person(name:String,age:Int) {
println("Person ...")
def walk():Unit = println("walk like a normal person")
}
//子类
//继承关系
class Student(name:String,age:Int,var stuNo:String) extends Person(name,age){
println("Student ...")
override def walk():Unit={
super.walk();//调用父类的walk方法
println("walk like a elegant swan")
}
}
object demo{
def main(args: Array[String]): Unit = {
//先执行父类构造再执行子类构造
val student = new Student("Tom",18,"10002")
//方法重写
student.walk()
}
}
class Person {
//构造函数-------------------------------------
var name:String = _;
var sex:String = _
var age:Int=0
def this(name:String,sex:String,age:Int){
this()
this.name = name
this.sex = sex
this.age = age
}
//----------------------------------------------------
def eat():Unit={
name="张三"
sex="男"
age = 18
println(s"$name,$sex,$age,会吃饭")
}
}
class Student extends Person{
override def eat(): Unit = {
var school:String = "清华"
var id:String = "12345"
super.eat()
println(s"$school+$id,我是子类学生")
}
}
object demo{
def main(args: Array[String]): Unit = {
var student = new Student()
student.eat()
}
}
多态
object duotai {
class Person{
var name:String = "张三"
var age:Int = 12
def work():Unit={
println("我会工作")
}
}
class Student extends Person{
override def work(): Unit = {
super.work()
println("我会学习")
}
}
class Worker extends Person{
override def work(): Unit = {
super.work()
println("我会盖大楼")
}
}
def main(args: Array[String]): Unit = {
var p:Person = new Student()
p.work()
}
}
数组
object Array {
def main(args: Array[String]): Unit = {
//不可变数组
var arr1 = new Array[String](3)
arr1(0) = "aaa"
arr1(1) = "bbb"
arr1(2) = "ccc"
for (i <- 0 to arr1.length-1){
print(arr1(i)+" ")
}
println()
//可变数组
var arr2 = new ArrayBuffer[Int]()
//增加元素
arr2 += 1
arr2.append(2)
//修改元素
arr2(1) = 7
//删除元素
arr2.remove(1)
for (i <- 0 to arr2.length-1){
print(arr2(i)+" ")
}
}
}
scala自带高阶函数
概述:可以接收一个函数的函数就是高阶函数,又称为算子
1.map
介绍:将集合中的每一个元素通过指定功能(函数)映射(转换)成新的结果集
需求:请将List(1,2,3)中的所有元素都*2,将结果放到一个新的集合中返回,即返回一个新的List(2,4,6)
①. 传统做法:遍历list集合,获取每一个元素*2,放入新的集合中
val list1 = List(1,2,3)
var list2 = List[Int]()
for (elem <- list1) {
list2 = list2 :+ elem*2
}
println(list2) //List(2, 4, 6)
传统写法总结:优点是比较直接、好理解
缺点是不够简洁、高效,没有体现函数式编程特点
②. 使用map函数
val list1 = List(1,2,3)
val list2 = list1.map(v=>v*2)
println(list2) //List(2, 4, 6)
③. 模拟实现map函数
class MyList {
var list1 = List(1,2,3)
var list2 = List[Int]()
def map(f:Int=>Int): List[Int] ={
for (elem <- list1) {
list2 = list2 :+ f(elem)
}
list2
}
}
object MyList{
def apply(): MyList = new MyList()
}
测试
var myList = MyList()
val list2: List[Int] = myList.map(v=>v*2)
println(list2)
2.flatten
介绍:flat即压扁,压平,扁平化。效果就是将集合中的每个元素的子元素映射到某个函数并返回新的集合
+var list = List(List(1,2,3),List(3,4,5))
val list2 = list.flatten
println(list2) // 输出结果:List(1, 2, 3, 3, 4, 5)
val list3 = List(Array("zhangsan","lisi"),Array("lisi","wangwu"))
println(list3.flatten)
3.flatMap
介绍:先执行map,在执行flatten
var list = List("zhangsan lisi","lisi wangwu","wangwu wangwu")
val list2 = list.flatMap(v=>v.split(" "))
println(list2) //输出:List(zhangsan, lisi, lisi, wangwu, wangwu, wangwu)
4.filter
介绍:将符合要求的数据,通过指定函数的筛选放置到新的集合中
需求:将集合中首字母为‘A’的筛选到新的集合
var list = List("Alice","Tom","Jack","Abc")
val list2 = list.filter(v => {
v.startsWith("A")
})
println(list2) //输出结果:List(Alice, Abc)
5.reduce聚合算子
介绍:对集合中的元素进行归约操作(算子就是做聚合处理)(出来的是一个值)
需求:计算List集合中所有元素的和
var list = List(1,2,6,8)
val i: Int = list.reduce((v1,v2)=>v1+v2)
println(i)
var result:Int = list.reduce((sum,elem)=>sum+elem) //.var补全代码
//写法二
var result2:Int = list.reduce(_+_)
6.fold
介绍:fold函数将上一步返回的值作为函数的第一个参数继续传递参与运算
需求: 计算List集合中所有元素的和
要求:输入和输出的类型必须保持一致
var list = List(1,2,6,8)
val i: Int = list.fold(5)((v1, v2) => {
v1 + v2
})
println(i)
上述代码等同于
list(5,1,2,6,8).reduce((v1, v2) => {
v1 + v2
})
7.sorted
介绍:对集合中的元素进行排序
var list = List(1,13,4,34)
println(list.sorted) //输出结果 List(1, 4, 13, 34)
println(list.sorted.reverse) //输出结果 List(34, 13, 4, 1)
8.sortBy
介绍:对集合中元组的某个元素进行排序
//List集合中存储一个学生的信息,分别是姓名和语文成绩,请按照语文成绩排序
var list:List[(String,Double)] = List(("张三",100),("李四",84),("王五",95),("赵六",30))
println(list.sortBy(t => t._2)) //List((赵六,30), (李四,84), (王五,95), (张三,100))
println(list.sortBy(t => t._2).reverse) //List((张三,100), (王五,95), (李四,84), (赵六,30)
//List集合中存储一个学生的信息,分别是姓名和语文成绩、数学成绩
//请按照语文成绩先排序,语文成绩相等者,按照数学成绩排序
var list2 = List(("张三",100,58),("李四",84,96),("王五",84,75),("赵六",30,61))
println(list2.sortBy(v => (v._2, v._3))(Ordering.Tuple2(Ordering.Int,Ordering.Int.reverse)))
//输出结果:List((赵六,30,61), (李四,84,96), (王五,84,75), (张三,100,58))
9.groupBy
介绍:根据集合中包含元组进行分组
var list = List(("张三",100),("张三",96),("李四",84),("李四",30))
//.groupBy(v=>v._1) 作用:根据遍历集合获取元组的第一个元素进行分组
val map: Map[String, List[(String, Int)]] = list.groupBy(v=>v._1)
println(map)
//Map(张三 -> List((张三,100), (张三,96)), 李四 -> List((李四,84), (李四,30)))
10.foreach
介绍:遍历集合中的元素
var list = List("张三","李四","王五")
list.foreach(v=>{
println(v)
})
关于高阶函数的形参(匿名函数)的写法
val list = List(1,2,3)
1.正常的写法
list.map( (v:Int) => v*2 )
2.如果可以正常推断出匿名函数的形参类型,则:Int可以省略
list.map( (v) => v*2 )
3.如果匿名函数的形参个数只有1个,则()小括号可以省略
list.map( v => v*2 )
list.reduce( (v1,v2) => v1+v2 ) //此处(v1,v2)对应的小括号不能省略
4.如果匿名函数的形参(每一个),在匿名函数的代码体中只出现一次,则可以将形参列表和=> 省略,并且在函数体中使用_代表形参
list.map( v => v*2 ) 对应简写 list.map( _*2 )
list.map( v => v*v ) 不能使用_简写
list.reduce( (v1,v2) => v1+v2 ) 对应简写 list.reduce( _+_ )
函数高级
1.闭包
闭包是一个函数,返回值依赖于声明在函数外部的一个或多个变量。闭包通常来讲可以简单的认为是可以访问一个函数里面局部变量的另外一个函数
介绍: 闭包就是一个函数和与其相关的引用环境组合的一个整体
闭包演示
def minus(x:Int)= (y:Int)=> x-y
val f1 = minus(20) //此处的f1就是闭包
f1(1) //输出19
f1(2) //输出18
详解:
① (y:Int)=> x-y
代表minus这个函数的返回值是一个匿名函数,那么将来这个匿名函数执行时就需要引入外部环境变量x
这就形成了闭包
② 当多次调用f(可以理解多次调用闭包) 发现使用的是同一个x,所以x不变
③ 在使用闭包时,主要搞清楚返回函数引用了函数外的哪些变量,因为他们会组合成一个整体
闭包实践:设计一个函数makeSuffix(suffix:String) 可以接收一个文件后缀名(比如.jpg) 并返回一个闭包
调用闭包,可以传入一个文件名,如果该文件名没有指定后缀,则返回文件名.jpg,否则直接返回原文件名
def makeSuffix(suffix:String) = {
(fileName:String)=>{
val bool: Boolean = fileName.endsWith(suffix)
if(bool) fileName else fileName+".jpg"
}
}
val f2 = makeSuffix(".jpg")
println(f2("aaa.jpg")) //输出aaa.jpg
println(f2("heihei")) //输出heihei.jpg
2.函数柯里化
介绍:函数编程中,接受多个参数的函数都可以转化为接收单个参数的函数,这个转化过程就叫柯里化
柯里化就是证明了函数只需要一个参数而已,不用设立柯里化存在的意义这样的命题。柯里化就是以函数为
主体这种思想发展的必然产生结果
需求:编写一个函数,接收两个整数,可以返回两个数的乘积
//常规方式完成
def test1(v1:Int,v2:Int)={
v1+v2
}
test1(10, 20)
//闭包
def test2(x:Int)= (y:Int)=>x+y
test2(10)(20)
//柯里化
def test3(x:Int)(y:Int)={
x+y
}
test3(10)(20)
spark
Key-Value pair类型
RDD的转换算子
groupByKey案例
作用:groupByKey也是对每个key进行操作,但只生成一个sequence。
需求:创建一个pairRDD,将相同key对应值聚合到一个sequence中,
并计算相同key对应值的相加结果。
//1.创建一个RDD
val rdd1: RDD[String] = sc.makeRDD(List("one", "two", "two", "three", "three", "three"))
//2.相同key对应值的相加结果
val rdd2: RDD[(String, Int)] = rdd1.map((_,1)).groupByKey().map(v=>(v._1,v._2.size))
//3.打印
rdd2.collect().foreach(println)
结果(three,3) (two,2) (one,1)
分析图
reduceBykey
在一个(K,V)的RDD上调用,返回一个(K,V)的RDD,使用指定的reduce函数,将相同key的值聚合到一起,reduce任务的个数可以通过第二个可选的参数来设置。
需求:创建一个pairRDD,计算相同key对应值的相加结果
//1.创建一个RDD
val rdd1: RDD[(String,Int)] = sc.makeRDD(List(("female",1),("male",5),("female",5),("male",2)))
//2.相同key对应值的相加结果
val rdd2: RDD[(String, Int)] = rdd1.reduceByKey(_+_)
//3.打印
rdd2.collect().foreach(println)
结果:(male,7) (female,6)
reduceByKey和groupByKey的区别
reduceByKey:按照key进行聚合,在shuffle之前有combine(预聚合)操作,返回结果是RDD[k,v].
groupByKey:按照key进行分组,直接进行shuffle。
开发指导:reduceByKey比groupByKey,建议使用。但是需要注意是否会影响业务逻辑。
aggregateByKey案例
参数:(zeroValue:U,[partitioner: Partitioner]) (seqOp: (U, V) => U,combOp: (U, U) => U)
作用:在kv对的RDD中,,按key将value进行分组合并,合并时,将每个value和初始值作为seq函数的参数,进行计算,返回的结果作为一个新的kv对,然后再将结果按照key进行合并,最后将每个分组的value传递给combine函数进行计算(先将前两个value进行计算,将返回结果和下一个value传给combine函数,以此类推),将key与计算结果作为一个新的kv对输出。
参数描述:
(1)zeroValue:给每一个分区中的每一个key一个初始值;
(2)seqOp:函数用于在每一个分区中用初始值逐步迭代value;
(3)combOp:函数用于合并每个分区中的结果。
需求:创建一个pairRDD,取出每个分区相同key对应值的最大值,然后相加
//1.创建一个RDD
val rdd1: RDD[(String,Int)] = sc.makeRDD(List(("a",3),("a",2),("c",4),("b",3),("c",6),("c",8)),2)
//2.取出每个分区相同key对应值的最大值
val rdd2: RDD[(String, Int)] = rdd1.aggregateByKey(0)((v1,v2)=> if(v1 > v2) v1 else v2 ,_+_)
//3.打印
rdd2.collect().foreach(println)
结果:(b,3) (a,3) (c,12)
sortByKey([ascending],[numTasks]) 案例
作用:在一个(K,V)的RDD上调用,K必须实现Ordered接口,返回一个按照key进行排序的(K,V)的RDD
需求:创建一个pairRDD,按照key的正序和倒序进行排序
//1.创建一个RDD
val rdd1: RDD[(Int,String)] = sc.makeRDD(List((3,"aa"),(6,"cc"),(2,"bb"),(1,"dd")))
//2.按照key的正序
rdd1.sortByKey(true).collect().foreach(print) //(1,dd)(2,bb)(3,aa)(6,cc)
//3.按照key的降序
rdd1.sortByKey(false).collect().foreach(print) //(6,cc)(3,aa)(2,bb)(1,dd)
mapValues案例
针对于(K,V)形式的类型只对V进行操作
需求:创建一个pairRDD,并将value添加字符串"_x"
//1.创建一个RDD
val rdd1: RDD[(Int,String)] = sc.makeRDD(List((1,"a"),(1,"d"),(2,"b"),(3,"c")))
//2.给value增加一个_x
val rdd2: RDD[(Int, String)] = rdd1.mapValues(v=>v+"_x")
//3.打印
rdd2.collect().foreach(print)
结果 (1,a_x)(1,d_x)(2,b_x)(3,c_x)
join(otherDataset,[numTasks]) 案例
作用:在类型为(K,V)和(K,W)的RDD上调用,返回一个相同key对应的所有元素对在一起的(K,(V,W))的RDD
需求:创建两个pairRDD,并将key相同的数据聚合到一个元组。
//1.创建一个RDD
val rdd1: RDD[(Int,String)] = sc.makeRDD(List((1,"a"),(2,"b"),(3,"c")))
val rdd2: RDD[(Int,Int)] = sc.makeRDD(List((1,4),(2,5),(3,6)))
//2.join操作
val rdd3: RDD[(Int, (String, Int))] = rdd1.join(rdd2)
//3.打印
rdd3.collect().foreach(println)
结果:(1,(a,4))
(2,(b,5))
(3,(c,6))
RDD的行动(Action)算子
spark调用rdd的转换算子,默认都是延迟执行的,只有调用行动算子才会触发之前的转换算子的调用
rdd调用转换算子返回的还是rdd对象,如果调用行动算子返回的是scala对象
只有rdd才可以调用spark中的转换或者行动算子
reduce(func)案例
作用:通过func函数聚集RDD中的所有元素,先聚合分区内数据,再聚合分区间数据。
需求:创建一个RDD,将所有元素聚合得到结果。
val rdd1 = sc.makeRDD(1 to 10)
println(rdd1.reduce(_ + _)) //输出 55
val rdd2 = sc.makeRDD(Array(("a",1),("a",3),("c",3),("d",5)))
println(rdd2.reduce((v1, v2) => (v1._1 + v2._1, v1._2 + v2._2))) //输出 (aacd,12)
collect()案例
作用:在驱动程序中,以数组的形式返回数据集的所有元素。
需求:创建一个RDD,并将RDD内容收集到Driver端打印
count()案例
作用:返回RDD中元素的个数
需求:创建一个RDD,统计该RDD的条数
val rdd1 = sc.makeRDD(1 to 10)
println(rdd1.count()) //输出 10
first()案例
作用:返回RDD中的第一个元素
需求:创建一个RDD,返回该RDD中的第一个元素
val rdd1 = sc.makeRDD(1 to 10)
println(rdd1.first())
take(n)案例
作用:返回一个由RDD的前n个元素组成的数组
需求:创建一个RDD,统计该RDD的条数
val rdd1 = sc.makeRDD(1 to 10)
println(rdd1.take(5).mkString(",")) //输出:1,2,3,4,5
takeOrdered(n)案例
作用:返回该RDD排序后的前n个元素组成的数组
需求:创建一个RDD,统计该RDD的条数
val rdd = sc.parallelize(Array(2,5,4,6,8,3))
println(rdd.takeOrdered(3).mkString(",")) //输出:2,3,4
aggregate案例
参数:(zeroValue: U)(seqOp: (U, T) ⇒ U, combOp: (U, U) ⇒ U)
作用:aggregate函数将每个分区里面的元素通过seqOp和初始值进行聚合,然后用combine函数将每个分区的结果和初始值(zeroValue)进行combine操作。这个函数最终返回的类型不需要和RDD中元素类型一致。
fold(num)(func)案例
作用:折叠操作,aggregate的简化操作,seqop和combop一样。
需求:创建一个RDD,将所有元素相加得到结果
saveAsTextFile(path)
作用:将数据集的元素以textfile的形式保存到HDFS文件系统或者其他支持的文件系统,对于每个元素,Spark将会调用toString方法,将它装换为文件中的文本
对应读取文件的方法:sc.textFile("")
saveAsSequenceFile(path)
作用:将数据集中的元素以Hadoop sequencefile的格式保存到指定的目录下,可以使HDFS或者其他Hadoop支持的文件系统。
针对rdd中的元素是(k,v)格式的数据进行保存
sc.sequenceFile()可以读取rdd.saveAsSequenceFile()保存的数据
saveAsObjectFile(path)
作用:用于将RDD中的元素序列化成对象,存储到文件中。
对保存的数据进行序列化
sc.objectFile("")
countByKey()案例
作用:针对(K,V)类型的RDD,返回一个(K,Int)的map,表示每一个key对应的元素个数。
需求:创建一个PairRDD,统计每种key的个数
val rdd = sc.parallelize(List((1,3),(1,2),(1,4),(2,3),(3,6),(3,8)))
val map: collection.Map[Int, Long] = rdd.countByKey()
println(map)
输出:Map(1 -> 3, 2 -> 1, 3 -> 2)
foreach(func)案例
作用:在数据集的每一个元素上,运行函数func进行更新。
需求:创建一个RDD,对每个元素进行打印
RDD的依赖关系
RDD依赖关系也称为RDD的血统,描述了RDD间的转换关系 。Spark将RDD间依赖关系分为了宽依赖|ShuffleDependency
、窄依赖|NarrowDependency
,Spark在提交任务的时候会根据转换算子逆向推导出所有的Stage。然后计算推导的stage的分区用于表示该Stage执行的并行度
。
窄依赖:
-
父RDD和子RDD partition之间的关系是一对一的。或者父RDD一个partition只对应一个子RDD的partition情况下的父RDD和子RDD partition关系是多对一的。不会有shuffle的产生。父RDD的一个分区去到子RDD的一个分区。
-
可以理解为独生子女
宽依赖:
-
父RDD与子RDD partition之间的关系是一对多。会有shuffle的产生。父RDD的一个分区的数据去到子RDD的不同分区里面。
-
可以理解为超生
DAG
DAG(Directed Acyclic Graph)叫做有向无环图,原始的RDD通过一系列的转换就就形成了DAG,根据RDD之间的依赖关系的不同将DAG划分成不同的Stage,对于窄依赖,partition的转换处理在Stage中完成计算。对于宽依赖,由于有Shuffle的存在,只能在parent RDD处理完成后,才能开始接下来的计算,因此宽依赖是划分stage的依据。
任务划分
RDD任务切分中间分为:Application、Job、Stage和Task
1)Application:初始化一个SparkContext即生成一个Application
2)Job:一个Action算子就会生成一个Job
3)Stage:根据RDD之间的依赖关系的不同将Job划分成不同的Stage,遇到一个宽依赖则划分一个Stage。
4)Task:Stage是一个TaskSet,将Stage划分的结果发送到不同的Executor执行即为一个Task。
注意:Application->Job->Stage-> Task每一层都是1对n的关系。