【Todo】【读书笔记】大数据Spark企业级实战版 & Scala学习
目录:/Users/baidu/Documents/Data/Interview/Hadoop-Spark-Storm-Kafka
下了这本《大数据Spark企业级实战版》,
另外还有一本《Spark大数据处理:技术、应用与性能优化(全)》
先看前一篇。
根据书里的前言里面,对于阅读顺序的建议。先看最后的Scala实践三部曲吧。
scala学习,我觉得这一段写的很好:
object Hello{ def main(args: Array[String]): Unit = { val ret = sum(x=> x*x)(1)(2) println(ret) } def sum(f: Int => Int)(a: Int)(b: Int): Int = if (a > b) 0 else f(a) + sum(f)(a+1)(b) }
能够看出,上面是求出从a加到b的平方和。很巧妙。
Scala中有两点需要注意:
1. 函数体的最后一行的值就是整个函数的返回值;
2. 类型的声明位于变量、函数或者类的后面。
当函数不带参数时候,我们调用的时候,可以不加括号。
函数还可以这样定义:
def add = (x:Int, y:Int)=>x+y
要注意一下Scala的柯里化,currying,允许函数定义时候有多个括号,每个括号里面一个参数。在 Haskell 中,所有的函数都是柯里化的.
科里化(柯里化)这种现象是随着函数被当做一等公民自然而然地产生的。不然高阶函数会很麻烦。
注意Java和Python里面,都有可变参数的,可以看我的这篇文章:http://www.cnblogs.com/charlesblc/p/6226667.html
Scala里面也有可变函数。
如下:
def abc(s: String*) = {
s.foreach(x=>println(x))
}
然后就可以调用了
abc("I", "love", "you")
默认参数是这样的:
def abc(name :String = "default") : String = {
...
}
for循环:
查看我的这篇文章:http://www.cnblogs.com/charlesblc/p/6065424.html
Scala里面的面向对象
面向对象几点:
抽象和封装,继承,多态(多态也称为一个名字,多种方法)
注意 Scala里面的下划线,用的非常多,有个名字叫作Placeholder,下面这篇文章介绍了十几种用法:
https://my.oschina.net/leejun2005/blog/405305
没怎么看懂,慢慢领会。
文中用到了其中的第11中用法:
11、初始化默认值:default value var i: Int = _
另外,还有private[this]的使用:
class A{
private[this] val gender = "male"
}
那么在外面访问 val a = new A
a.gender 就会报错
看看主构造器的用法。有如下特点:
1. 主构造器直接跟在类名后面,参数会被编译成类的字段。
2. 执行时会执行类中的所有不包含在方法体中的语句。
class Person(val name: String, val age: Int) {
println("this is constructor!")
}
运行 val p = new Person("Rocky", 27) 会打印语句。
注意:
class Person(name: String, val age: Int) {
}
这样的话,运行会报错,找不到name,说明没有用val或者var加载主构造器函数的话,那么这个变量是 private[this]的,只能内部访问。(那类岂不是不能初始化?)
附属构造器
1) 附属构造器是通过this来声明的
2) 附属构造器必须调用主构造器或者其他附属构造器。
class Person(var name : String, val age: Int) {
var gender : String = _
def this(name: String, age: Int, gender: String) {
this(name, age)
this.gender = gender
}
}
继承
Scala继承用 extends来进行, 覆盖用 override来处理。
抽象类
abstract class A {
def speak
}
另外
object AAAClass extends App {
val worker = new Worker
worker.speak
}
App是trait的子类,内部帮助我们实现了main方法。
Scala的trait
trait支持部分实现,也就是说可以在scala的trait中可以实现部分方法。
trait可以有实现的方法,也可以有抽象方法。使用trait的方式是with而混入类中。不懂。
子trait可以覆盖父trait中的方法,如果父trait中已经实现了方法,子trait就必须用override关键字。
如果既要继承类,又要继承trait,可以用 with关键字,如下:
class MyAccount extends Account with FLogger {
def save {
log("10000")
}
}
其中 log 是定义在trait Flogger里面的函数。
然后在定义的时候,要这样写:
val acc = new MyAccount with MessageLogger
acc.save
上面的MessageLogger是实现了 Flogger 的trait,如下:
trait MessageLogger extends Flogger {
override def log(msg: String) {
}
}
另外,放在object里面的方法都是static方法,直接调用:
object abc {
def func{
}
}
abc.func
apply在object和class里面的应用
object里面可以定义apply
class A {
def test() {
println("test")
}
}
object A {
def apply() = new A
}
val a = A()
a.test
上面的 A() 直接调用 object A里面的apply(),返回了 class A,所以最后打印 "test"
class里面也可以定义apply
class A {
def apply() = "hi"
}
val a = new A
println(a())
上面的 () 调用了 apply(),所以打印了"hi"
因为object里面的方法和属性都是static的,所以用来实现单例,很方便。
object A {
def apply() = new A
var count = 0
def incr = {
count = count + 1
}
}
用法:
for (i <- 1 to 10) {
A.incr
}
println(A.count)
打印了10
总结:object本身就是一个单例对象!!!
Scala函数式编程
P773
函数式编程的核心特点之一,就是把函数作为参数传给函数、在函数内部可以定义函数等。
1. 函数式编程定义:其实是方法论(programming paradigm)
5个特点:
1. 函数是第一等公民
2. 总用表达式 expression,不用语句statement,(意思是总有返回值)
3. 没有副作用 (避免全局变量)
4. 不修改状态(只返回新的值,不修改系统变量)
5. 引用透明(运行只依赖于输入的参数)
好处:
1. 代码简洁,开发迅速; 2. 接近自然语言; 3. 方便的代码管理(不依赖外界状态)4. 并发编程方便 5. 易于热升级(只要接口不变,内部实现外部无关,erlang就是为了不关机升级)
Scala的函数形式:
def func(var : type) : retType = {}
返回类型,有时候可以直接推断出;但是写出来更好。如果是递归的,那么返回类型必须明确写出来。
如果函数体只是一句,也可以不加花括号。
Unit是返回类型,指的是没有有效的返回值,有点类似于Java的void。Java中返回void的方法,会被映射成Scala返回Unit的方法。
值函数
值函数指的就是将一个函数赋值给一个变量进行保存,这时候变量就变成了一个函数,用的时候跟函数一样用就可以了。
def add(x:Int, y:Int):Int = (x+y)
var result = add _ (注意,把函数赋值给变量的时候,必须在后面加上空格和_)
result(1, 2) 得到3
匿名函数
(x:Int) => x + 3
可以赋值给一个常量: val fun = (x: Int) => x + 3
这就相当于 def fun(x: Int) = x + 3
调用是这样的 fun(7)
主要用途是作为参数传递,比如:
scala.collection.mutable.ArrayBuffer(1,2,3,4).map((x:Int)=>x+3)
Scala中的闭包
闭包 = 代码 + 用到的非局部变量
var y = 1
val sum = (x : Int) => x + y
sum(15)
这时候y是外部变量。
Scala中的SAM
Java中有些接口只有单个抽象方法(Single Abstract Method),在Java里面被称为 SAM类型。
为了在传入Java ActionListener对象的地方,传入 (ActionEvent)=>Unit函数参数,需要加一个隐式转换。
Scala中的Curry
柯里化,指的就是都变成一个参数的函数,新的函数返回一个以原有第二个参数为参数的函数。
def multi(x:Int) = (y: Int) => x * y
multi(6)(7)
柯里化可以简写成:
def multi(x:Int)(y:Int) = x * y
控制抽象 + 换名调用参数
可以组成不带参数也没有返回值的函数:
def runInThread(block: ()=>Unit) {
new Thread {
override def run() {block()}
}.start()
}
注意:如果方法的返回类型为Unit,则可以忽略result type 和 = 号。
runInThread{ ()=> println("Hi"); Thread.sleep(10000); println("Bye")}
可以去掉()=>,
def runInThread(block: =>Unit) {
new Thread {
override def run() {block}
}.start()
}
runInThread {println("Hi"); Thread.sleep(10000); println("Bye")}
Scala程序员可以构建控制抽象,看起来就像是关键字:
def until(condition: => Boolean) (block: =>Unit) {
if (!condition) {
block
until(condition)(block)
}
}
// 使用
var x = 10
until(x == 0) {
x -= 1
println(x)
}
这样的函数就叫做换名调用函数(常规的参数叫作换值调用参数)。函数在调用的时候,换名调用参数的表达式不会求值,表达式会被当做参数传递下去。
return表达式
P783 略
高阶函数
函数作为参数或作为返回值的函数称为 高阶函数。
val a = List(1,2,3)
这里能够直接使用List实例化对象,其实是用了List的object对象的apply方法。
val newList = l.map((x:Int)=>2*x)
类型一样的话可以省略类型 l.map((x)=>2*x)
只有一个参数,可以省略括号 l.map(x=>2*x)
只有一个参数,可以继续省略 l.map(_*2)
常见的高阶函数有 map, filter, reduce
1. map
array.map(1 + _) 其中的 _代表列表里面的每一个元素
2. filter
array.filter( _ > 33) 大于33的
3. reduce
array.reduce(_ - _) 是第一个减去第二个,然后结果再减去第三个
Scala中的集合
主要有 List, Set, Tuple, Map等
关于 Array, List, Tuple的区别,可以看这篇文章:
https://my.oschina.net/u/1034176/blog/512314
在Scala 2.7中,Array、List都不能混合类型,只有Tuple可以;而在Scala以上版本中,3者的元素都可以混合不同的类型(转化为Any类型),只不过是当使用混合类型时,Array和List会将元素类型转化为Any类型,而Tuple则保留每一个元素的初始类型;
-
关于初始化
1) Array:val array= new Array[String](3) // Array(null, null, null)相当于声明了3个null值的空元素
val array= Array("a","b","c","d") // 相当于 Array.apply("a","b","c","d")
定义一个类型为Any的Array:
val aa = Array[Any](1, 2)或:val aa: Array[Any] = Array(1, 2)或:val aa: Array[_] = Array(1, 2)
2) List:
val list:List[Int] = List(1,3,4,5,6) // 或者 val list = List(1,3,4,5,6)
(:::)实现叠加List,(::)cons:将新元素组合到列表的最前端。元素合并使用::,集合合并使用:::,示例如下:其中Nil代表空元素
val list2 = "a"::"b"::"c"::Nil // Nil
val list4 = list2:::list3
3) Tuple:
元组也是不可变的,但是元组可以是不同类型的数据,实例化:var a = (,),可以通过点号,下划线,-N(N从1开始)的索引访问元素
对Tuple而言,如果只有两个元素,还可以通过下面的方式创建:
"a" -> "b"
得到:res7: (String, String) = (a, b)
Map类型
Map("a"->"b", "c"->"d")
Option类型
Option代表一个可有可无的值。
Option有两个子类:Some 和 None . 下面我们看一下Option的使用。
Option[T] 是一个类型为 T 的可选值的容器: 如果值存在, Option[T] 就是一个 Some[T] ,如果不存在, Option[T] 就是对象 None 。
优点大概是让有值和无值的操作变得统一吧。
filter的处理
下面两个是等价的:
l.filter(x=>x %2 ==0)
l.filter(x%2 == 0)
看一下zip的操作
partition的操作
flatten 和 flatMap 的操作
Scala中的泛型
p790
在Scala中用 [] 来代替Java中的 <> 来表现类型参数表。
Scala中的隐式转换、隐式参数、隐式类
P784
泛型和隐式转换都看不懂。以后再看吧。
回到第13页,第一章,开始看起。
Spark术语
Spark的容错主要是 Lineage机制。 分布式数据容错主要方式有:数据检查点,和记录数据的更新。 Spark是粗粒度的记录数据的更新,只记录数据怎么从其他RDD转换而来。
RDD依赖关系
Stage DAG
通常Shuffle是Stage的边界
看到第36页。
持久化与persist
通常每次运行会重新计算,如果不想重复计算,可以用 RDD.persist(). 会存储在内存里(或者磁盘?)
不是必须,不应该持久化,因为浪费空间。
如果多次需要结果,可以持久化,如下:
right.persist()
right.first()
right.count()
另外关于 cache(),查了一下,就是全内存化的persist(),是用persist()实现的,即 persist(StorageLevel.MEMORY_ONLY)
创建RDD
Spark提供了两种创建RDD的方式:加载外部数据集,和在驱动程序中平行化集合。
分别是:
val lines = spark.textFile("hdfs://master:9000/input/in.txt")
和
val lines = spark.parallelize(List("pandas", "i like pandas"))
RDD操作
P38 下面这段讲的非常好:
转换和动作的示例
用take()可以检索一个小数目的结果。
还有一个collect()会输出全部结果。但是量比较大。可以使用 saveAsTextFile() 或者 saveAsSequenceFile()来存储到HDFS上。
惰性评估(Lazy Evaluation)
比如 sc.textFile(),数据没有被加载,只有到动作需要执行时候,才会真正加载。
Spark子框架解析
P30
Spark GraphX 看了一会 不懂。
Spark Stream
是按照时间节点,比如2秒,分成一段一段的,作为Dstream(Discretized Stream),然后这一段数据转换成RDD,那么Spark Streaming对于Dstream的操作就转换成了 Spark对于RDD的操作。
流程图如下:
Spark Streaming编程模型
P55 主要是这一句:
val wordCount = words.map(x=>(x,1)).reduceByKeyAndWindow(_+_, Seconds(5s), seconds(1))
P57
Kafka 和 Spark Streaming结合。
Spark SQL
P58
Spark MLlib
P61
上面提到的L1和L2,在下面这篇文章讲得很好:
http://blog.csdn.net/jinping_shi/article/details/52433975
L1正则和L2正则,其实都是加在损失函数后面的一个额外项。L1正则化和L2正则化可以看做是损失函数的惩罚项。
对于线性回归模型,使用L1正则化的模型建叫做Lasso回归,使用L2正则化的模型叫做Ridge回归(岭回归)。下图是Python中Lasso回归的损失函数,式中加号后面一项α||w||1即为L1正则化项。
下图是Python中Ridge回归的损失函数,式中加号后面一项α||w||22即为L2正则化项。
一般回归分析中回归w表示特征的系数,从上式可以看到正则化项是对系数做了处理。L1正则化和L2正则化的说明如下:
- L1正则化是指权值向量w中各个元素的绝对值之和,通常表示为||w||1
- L2正则化是指权值向量w中各个元素的平方和然后再求平方根(可以看到Ridge回归的L2正则化项有平方符号),通常表示为||w||2
一般都会在正则化项之前添加一个系数,Python中用α表示,一些文章也用λ表示。这个系数需要用户指定。
那添加L1和L2正则化有什么用?下面是L1正则化和L2正则化的作用,这些表述可以在很多文章中找到。
- L1正则化可以产生稀疏权值矩阵,即产生一个稀疏模型,因此可以用于特征选择
- L2正则化可以防止模型过拟合(overfitting);一定程度上,L1也可以防止过拟合
原因和解释,可以看上面那篇文章的原文。不细说了。
聚类
聚类是一种非监督学习。聚类常被用于探索性分析,或者作为层次化监督学习的一部分(聚类之后再对不同的类簇采用不同的分类器或者回归模型)。
MLLib 实现了 kmeans.
协同过滤
注意显性反馈与隐性反馈。
目前Spark里面可用的算法:
ALS
基础算法-梯度下降算法
P63
二元分类 线性回归 聚类 协同过滤ALS 例子
P64
第四章 Spark RDD与编程API实战
P171
P193
通过 toDebugString 函数可以查看 lineage信息。
P195
有一个实战搜狗日志的例子,不知道是不是跟之前的实战例子类似。
P198
实例,按条件搜索:
实例,排序,按照val排序的方式
P206
Spark支持的Transformation操作
和
Spark支持的Action操作
P219
Spark运行的主要流程,包括Master,Driver,Worker,DAGScheduler各自参与的工作。
P259
6.1 Spark内核核心术语
Application 等术语的解释和描述。
看到P299 GraphX 图运算 先跳过不看
先看P431里面的 Spark MLLib吧
还有 P665的第14章 性能调优 可以看,锦上添花那种。
MLLib实际对应 P443
P444介绍了机器学习的基本概念:
讲了各种机器学习算法。
P455 介绍了一个基于Spark MLLib的SVM的实例。
代码非常简洁明了。
P474
MLLib经典算法案例解析(重点看线性回归、协同过滤)