一、上次课回顾
1、注意源数据与元数据的区别,源数据对于Mapreduce来说的话就相当于是输入的数据;而元数据是描述数据的数据
2、Hive对应到MySQL元数据库中的一些表,tbls dbl表的关联关系
3、集群数据规模的评估,多少机器、多少业务线、几副本、数据要存几年,拿到磁盘的空间,需要预留30%-50%的空间,为了避免数据突增;比如系统OOM,mapreduce达到90%阈值时就会变成unhealthy nodes,节点不健康
二、Scala的变量var、val
在Linux的控制台上操作Scala:
1、打开进入Scala后,开始运行计算默认就是从res0开始递增
scala> 1+1
res0: Int = 2
var和val用来修饰对象:
1、使用var修饰对象
1、定义一个人名字为17
scala> var name:String="17"
name: String = 17
2、查看名字
scala> name
res2: String = 17
3、名字修改为17er
scala> name="17er"
name: String = 17er
4、查看已经修改成功
scala> name
res3: String = 17er
2、使用val修饰对象
1、使用val定义人名
scala> val name="17"
name: String = 17
2、修改人名,提示重新分配失败
scala> name="17er"
<console>:12: error: reassignment to val
name="17er"
^
3、Scala不定义类型还能够自己识别类型:
scala> val name = "big tree"
name: String = big tree
小结:
- val修饰的是不可变的,只能是值,var修饰的可以是变量
- Scala中的类型是可以自动推导的
2.1、Scala的类型&&类型转换
Scala中的数据类型:
Byte、Char、Short、Int、Long、Float、Double、Boolean
类型转换和类型判断:
1、asInstanceOf表示类型转换:
scala> val b=10.asInstanceOf[Double]
b: Double = 10.0
2、isInstanceOf表示类型判断:
scala> val b=10.5f.isInstanceOf[Float]
b: Boolean = true
三、函数、方法
3.1、函数的定义
-
通用框架:
def main(x:Int,y:Int):Int= { if (x>y){ x }else{ y } }
解析:
- main后面跟着的是 (x:Int,y:Int) 函数的入参,可能没有,可能有多个,入参之间使用逗号分隔;每一个参数的定义:参数名称:参数类型
- def:定义函数的关键字、max:函数的名称,需要见名知意
- 在两个大括号之间的叫做方法体/函数体,函数体的最后一行表示返回值,return xx
- :Int表示的是一个返回值类型
-
定义sum方法,传入x/y两个参数,然后调用sum方法
object Function { def main(args: Array[String]): Unit = { print(sum(2,3)) } def sum(x:Int,y:Int):Int={ x+y } }
注意:
基于IDEA开发要注意返回值,比如你自己定义方法的时候,直接回车的时候自动跳出Unit,如下:
1、如下是取不到值的:因为返回值是Unit:
def sum(x:Int,y:Int){
x+y
}
3.2、函数的调用
object Function {
def main(args: Array[String]): Unit = {
//调用sayHello方法
sayHello()
sayHello //函数没有入参,不加括号也可以
sayWorld("john")
sayWorld //不带括号编译都编译不过去
}
//定义一个sayHello方法
def sayHello():Unit={
println("hello")
}
//定义一个sayWorld方法
def sayWorld(a:String): Unit = {
println(a + "好开心")
}
}
3.3、函数循环表达式
在Linux上查看:
1、查看到它底层调用的是Range方法:
scala> 1 to 10
res4: scala.collection.immutable.Range.Inclusive = Range(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
2、直接使用Range方法:
scala> Range(1,10)
res6: scala.collection.immutable.Range = Range(1, 2, 3, 4, 5, 6, 7, 8, 9)
3、还可以加上步长:
scala> Range(1,15,4)
res2: scala.collection.immutable.Range = Range(1, 5, 9, 13)
4、写两个参数代表步长是1,需要步长是-3
scala> Range(10,1)
res4: scala.collection.immutable.Range = Range()
scala> Range(10,1,-3)
res3: scala.collection.immutable.Range = Range(10, 7, 4)
小结:
1 to 10 --> 1.to(10)
1 until 10
Range(1,10)
scala> Range(1,10,0)
java.lang.IllegalArgumentException: step cannot be 0.
at scala.collection.immutable.Range.(Range.scala:86)
at scala.collection.immutable.Range$.apply(Range.scala:439)
… 32 elided
- 步长不能为0
去到IDEA中的Range源码中寻找:
@param start the start of this range.
* @param end the end of the range. For exclusive ranges, e.g.
* `Range(0,3)` or `(0 until 3)`, this is one
* step past the last one in the range. For inclusive
* ranges, e.g. `Range.inclusive(0,3)` or `(0 to 3)`,
* it may be in the range if it is not skipped by the step size.
* To find the last element inside a non-empty range,
use `last` instead.
* @param step the step for the range.
to的底层就是调用range的:
def to(end: Int): Range.Inclusive = Range.inclusive(self, end)
四、面向对象编程
4.1、面向对象-从类开始
在Scala中,class来定义类,可以理解为某一种、某一类东西的一个抽象;
如下代码解析:
-
先class People,定义出People类,这个类有两个属性,name、city属性;
-
这个类有两个方法,sleep和py方法;sleep方法有返回值,无传入参数;py方法有传入参数,无返回值;
-
使用这个类的时候要先new一个人出来,val people = new People;然后people.name=“sail”,为new出来的对象进行赋值,sleep方法有返回值,可以直接print进行打印;
-
people.py(这个方法需要传入一个参数),所有的操作都是基于new出来的这个people对象来的;
object demo { def main(args: Array[String]): Unit={ val people = new People //先new出来一个人 people.name="sail" println(people.sleep()) people.py("sail")
}
class People{ //定义了人的属性 var name="" //""表示这个人肯定是一个字符串类型 var city="" def sleep(): String ={ name + "正在睡觉ZZZ" } def py(pyname:String)={ println(name + "正在和" + pyname + "做一些羞羞的事情,,,") } }
}
补充:
1、val 属性=_,表示占位符,如下的都是可以这样使用的
var city = _
//上面这个定义会报错,unbound placeholder parameter,报错的原因,是不知道city的类型是什么,所以会报错
var city:String = _
var city1 = ""
var city2:String = ""
4.2、定义SparkConf
使用面向对象的方法定义SparkConf如下:
解析:
1、定义一个SparkConf类,有appName和master两个属性;
2、定义两个方法,setAppName/setMaster方法,使用this关键字:用于引用当前对象;
3、new一个sparkconf对象,为sparkconf对象赋值,this方法就是赋什么值就打印什么值;
4、定义一个printInfo方法,把传入的两个参数打印出来;
object SparkConfApp {
def main(args: Array[String]): Unit = {
val sparkconf = new SparkConf
sparkconf.setAppName("SparkConfApp")
sparkconf.setMaster("yarn")
sparkconf.printInfo()
}
class SparkConf{
var master:String= _
var appName:String=_
def setMaster(master:String)={
this.master=master
}
def setAppName(name:String)={
this.appName=name
}
def printInfo()={
println(this.appName +":"+ this.master)
}
}
}
引申到Spark官网上去:
- http://spark.apache.org/docs/latest/rdd-programming-guide.html#initializing-spark
注意到如下这段话:
val conf = new SparkConf().setAppName(appName).setMaster(master)
new SparkContext(conf)
所以上面的面向对象方法需要修改成通用形态:
-
在sparkconf中可以设置非常多的东西,做一个通用的方法
-
定义一个set方法,有key/value,返回一个SparkConf,之前是一个一个设置–>一批一批设置,key,value的底层结构是map,我们定义一个属性setting,可以支持并发(var setting = new ConcurrentHashMap)
-
setting.put(key,value) -->就可以这样传值;
-
此时需要修改printInfo方法,取值的时候也要在setting中去取值,setting.get(“spark.app.name”)
import java.util.concurrent.ConcurrentHashMap
object SparkConfApp {
def main(args: Array[String]): Unit = { val sparkconf = new SparkConf sparkconf.setAppName("SparkConfApp").setMaster("yarn") sparkconf.printInfo() } class SparkConf{ def setMaster(master:String): SparkConf={ set("spark.master",master) } def setAppName(name:String): SparkConf={ set("spark.app.name",name) } def printInfo()={ val appName = setting.get("spark.app.name") val master = setting.get("spark.master") println(appName + ":" + master) } //key,value --> map var setting = new ConcurrentHashMap[String,String]() def set(key:String,value:String):SparkConf = { setting.put(key,value) this } }
}
这样就可以把值取出来了,还可以进行修改:
def main(args: Array[String]): Unit = {
val sparkconf = new SparkConf
// sparkconf.setAppName("SparkConfApp").setMaster("yarn")
sparkconf.set("spark.app.name","SparkConfApp").set("spark.master","yarn")
sparkconf.printInfo()
}
扩展语法:private[this]
private[this] def set(key:String,value:String): SparkConf = {
setting.put(key,value)
this
}
这样修改的话:sparkconf.set(“spark.app.name”,“SparkConfApp”)就会出现报错,所以只能那个使用sparkconf.setAppName().setMaster()
private修饰方法只有在class里面使用,外面是使用不了它的
IDEA中查看SparkConf.scala源码:
需要在pom.xml中添加相应的依赖:
<spark.version>2.4.2</spark.version>
<!--Spark Core的依赖-->
<dependency>
<groupId>org.apache.spark</groupId>
<artifactId>spark-core_2.11</artifactId>
<version>${spark.version}</version>
</dependency>
-
查看源码:
1、ctrl+o,查看所有的方法
2、SparkConf中的代码是怎么写的呢,也是先定义一个SparkConf类,有一个私有的setting,查看setMaser方法def setMaster(master: String): SparkConf = {
set(“spark.master”, master)
}
点击这个set,跳转到相应源码:
private[spark] def set(key: String, value: String, silent: Boolean): SparkConf = {
if (key == null) {
throw new NullPointerException(“null key”)
}
if (value == null) {
throw new NullPointerException("null value for " + key)
}
if (!silent) {
logDeprecationWarning(key)
}
settings.put(key, value)
this
}
4.3、构造方法(主构造器和附属构造器)
我们定义人名、城市的时候是把它放在属性中的,创建People类的时候,直接可以传入它的属性进去;
主构造器:
1、类后面可以直接跟上属性(叫做主构造器),然后new对象的时候属性也能直接跟在后面,打印的时候会先打印主构造器中的方法:
object test {
def main(args: Array[String]): Unit = {
val person = new Person("john","苏州")
println(person.name + "" + person.city)
}
class Person(var name:String,var city:String){
println("people 1")
println("people 2")
}
}
附属构造器:在主构造器中的:
object test {
def main(args: Array[String]): Unit = {
val person = new Person("john","苏州")
println(person.name + "" + person.city)
val person2 = new Person("sail","北京",30)
println(person2.name + "" + person2.city + "" + person2.age)
}
//如下的这个就是主构造器
class Person(var name:String,var city:String){
println("people 1")
//附属构造器的第一行必须要调用主构造器或者其它附属构造器
var age:Int = _
def this(name:String,city:String,age:Int){
this(name,city)
this.age=age
}
println("people 2")
}
打印结果:
people 1
people 2
john苏州
people 1
people 2
sail北京30
说明每执行一次都会调用一次主构造器中的方法