(一)Spark學習筆記——Scala

2020-08-15 17:08:20

Spark也支援Java和Python,爲啥要學Scala?因爲Spark的原生語言是Scala,對Scala的支援最好,我覺得,Scala像是Java和Python的結合體,學着還挺好玩的

一、基本語法

1.宣告值和變數:

val: (變數指向的內容)不可變,宣告必須初始化,不能再賦值
var:(變數指向的內容)可變,宣告需要初始化,可以再賦值
例子:

// import java.lang._	// 預設會匯入java.lang包裡所有的東西,scala裡不用*,用_
val myStr	// 會報錯,val變數指向的內容宣告必須初始化
val myStr : String = "Hello World"
val myStr = "Hello World"  // 型別寫不寫都行,不寫冒號也不要有,他會自己推斷
println(myStr)
myStr = "Error"   // 會報錯,val變數指向的內容是不可變的

var myStr1 = "Hello World"
myStr1 = "Success"  // 不會報錯,var變數指向的內容是可變的

<=> Java

java.lang.String myStr = "Hello World"
System.out.println(myStr)

2.基本數據型別和操作

說明:下面 下麪用到符號「<=>」代表「等價於」
(1)Byte,Char,Short,Int,Long,Float,Double,Boolean 都是類
(2)操作符都是方法:

5 + 3 <=> (5).+(3)	
var i = 1
i ++ // 沒有 ++,--操作
i += 1
i -= 1

(3)回圈:Range

for(i <- 1 to 5) <=> for(i <- 1.to(5)) <=> for(i <- Range(1,2,3,4,5))

for(i <- 1 until 5) <=> for(i <- Range(1, 2, 3, 4))

for(i <- 1 to 10 by 2) <=> for(i <- Range(1, 3, 5, 7, 9))

for(i <- 0.5f to 5.9f by 0.8f) <=> for(i <- Range(0.5, 1.3, 2.1, 2.8999, 
3.699, 4.5, 5.3))

for(i<- 1 to 5 if i%2==0) println(i)

for(i<- 1 to 5; j <- 1 to 3) print(i*j)

//遍歷完把結果儲存下來,生成一個新變數
val r = for (i <- 1 to 5 if i % 2 == 0) yield {println(i); i}	
=> r = {2, 4}

3.讀寫檔案

(1)讀檔案

import scala.io.Source
val inputFile = Source.fromFile("output.txt")
var lines = inputFile.getLines	// 返回一個迭代器
for (line <- lines) println(line)

(2)寫檔案

import java.io.PrintWriter
val out = new PrintWriter(‘output.txt’)
for(i <- 1 to 5) out.println(i)
out.close()

4.例外處理

import java.io.FileReader
import java.io.FileNotFoundException
import java.io.IOException
try{
	val f = new FileReader(‘’)
}catch{
	case ex: FileNotFoundException =>
		//
	case ex: IOException => 
		//
}finally{
	file.close()
}

二、數據結構

scala中常用的集合Collection包括 List, Array, Set, Map
集合分爲「可變集合」和「不可變集合」兩種:
可變集合在包scala.collection.mutable
不可變集合在包scala.collection.immutable
預設的集合是不可變的

1.List

構造:

val intList = 1::2::3::Nil <=> val intList = List(1, 2, 3)	// Nil代表空列表物件

val strList = ("BigData", "Hadoop", "Spark")
val otherList = "Apache"::strList	//::代表在已有列表前端增加元素,右結合
=> otherList = ("Apache", "BigData", "Hadoop", "Spark")

操作

strList.head()	//返回第一個值
strList.tail()	//返回去掉第一個之後的列表

2.Map

構造

val university = Map("XMU" -> "Xiamen University")

新增元素

university("FZU") = "Fuzhou"
university += ("TJU" -> "Tianjin University",)

3.迭代器

val iter = Iterator(「a」, 「b」, 「c」)
while(iter.hasNext){}
for(ele <- iter){}

Iterable有兩個方法返回迭代器:grouped和sliding

lst = List(1, 2, 3, 4, 5)
lst grouped 3	// 分塊grouped,將列表以長度3進行分塊
=> .next=List(1, 2, 3), .next()=List(4, 5)
lst sliding 3	// 滑動sliding,視窗長度爲3,每次向後滑動1個單位長度
=> .next=List(1, 2, 3), .next()=List(2, 3, 4), .next()=List(3, 4, 5)

4.陣列:可變,可索引,相同類型

val intArr = new Array[int](3, 4,) // 一維
val intValueArr = Array(Array(12, 34, 45),)	// 二維
intValueArr(0)(1) = 12	// 下標用圓括號
intValueArr += 10 //相當於append
intValueArr -= 40 //把第一個40刪掉

5.元組:不同類型

val t = ("a", 23, 5.2)
print(t._1)		// 輸出第一個值

三、類

1.方法

def increment(): Unit = {}	//Unit <=> void,標準寫法
def a(value: int): Unit = value += 1	<=> 	def a(value: int){value += 1}	// 方法體只有一句時可以省略{}或者 : Unit =
def a() = {
	a	// 返回值,不需要寫return
}

2.編譯和執行

(1)先定義個類

class Counter{
	def a(): Int = {}
}
val myC = new Counter	// 沒參數,可以不寫括號
myC.a()	// 這個括號也可以不寫

編譯(在scala直譯器中):
第一種:scala Test.scala // 定義的是類可以這麼編譯,後面定義Object就不能這麼幹了
第二種:進入到直譯器下面 下麪執行:load ./Test.scala 退出:quit
(2)加上單例物件

class Counter{
	def a(): Int = {}
}
object MyCounter{
	def main(args:Array[String]){
		val myC = new Counter
		myC.a()
	}
}

有class必須:

scalac Test.scala
scala -classpath . MyCounter	// scala -classpath 路徑 類名

沒有class可以:

scala Test.scala

3. getter/setter方法

class Counter{
	private var privateVal = 0
	def value = privateVal	// getter
	def value_ = (newValue: Int){	// setter
		if (newValue>0)	privateVal = newValue
	}
	def a(): Int = {}
}
object MyCounter{
	def main(args:Array[String]){
		val myC = new Counter
		println(myC.value)	// 呼叫getter
		myC.value = 3	// 呼叫setter
	}
}

4. 構造器

輔構造器必須呼叫之前已經定義的主構造器或輔構造器

class Counter(val id: Int){	// 主構造器
	private var name = ""
	private var mode = 	1
	def a(): Int = {}
	def this(id: Int, name: String){
		this(id)	// 呼叫主構造器
		this.name = name
	}
	def this(id: Int, name: String, mode: Int){
		this(id, name)	// 呼叫輔構造器
		this.mode = mode
	}
}

5. 單例物件:不定義類也行,類似於java裡的static

object Person{} 	
val a = Person() // 不需要new

伴生物件:同一個檔案中,class後面的名稱和object後面的名稱完全一致的時候,class的是伴生類,object是伴生物件,伴生類可以直接呼叫伴生物件裡的方法,伴生物件裏面的所有方法都是靜態方法
編譯之後,伴生物件裡的內容都變成伴生類裡的靜態屬性/方法
有範例之後再用範例呼叫方法纔是呼叫伴生類

6. apply 和 update

apply:用括號傳遞給變數(物件)一個或多個參數時,Scala會把它轉換成對apply的呼叫
update:當對帶有括號幷包括一到若幹參數的物件進行賦值時,編譯器將呼叫物件的update方法,呼叫時,是把括號裡的參數和等號右邊的物件一起作爲update方法的輸入參數來執行呼叫的

7. 繼承

(1)重寫一個非抽象方法必須使用override修飾符,抽象方法不需要
(2)只有主構造器可以呼叫超類的主構造器
抽象類:

abstract class Car{	//不能範例化
	val carBrand: String	// 無初始值,抽象欄位
	def info()	// 抽象方法,無需abstract
	def greeting(){print()}
}

子類

class BMWCar extends Car {
	override val carbrand="BMW"	// 重寫超類欄位,需要使用override關鍵字
	def info(){print()}	// 重寫超類的抽象方法時,不需要override關鍵字
	override def greeting(){print()}	// 重寫超類的非抽象方法,必須使用override關鍵字
}

8. 特質 trait

(1)類似於java裡的介面,但又不僅實現了介面的功能,還具備很多其他特性
(2)是程式碼重用的基本單元,可以同時擁有抽象方法和具體方法
(3)一個類只能繼承自一個超類,卻可以實現多個特質,實現了多重繼承

trait CarId{
	var id: Int
	def curentId(): Int
}
trait CarGreeting{
	def greeting(msg: String){println(..)}	// 可以具體實現
}

可以使用extends或with混入類中

class BMWCar extends CarId with CarGreeting{
	override var id = 1000
	def curentId(): Int = {id += 1; id}
}

9. 模式匹配

(1)例一

val colorStr = colorNum match {
	case 1 => "red"
	case 2 => "green"
	case _ => "not allowed"	// other
|	case unexpected => unexpected + " is not allowed"
}

(2)例二

for (elem <- List(9, 12.3, "Spark", "Hadoop", "Hello")){
	val str = elem match{
		case i: Int => i + " is an int value."
		case d: Double => d + " is a double value."
		case "Spark" => "Spark is found"
		case _ => "This is an unexpected value."
	}
}

(3)例三

for (elem <- List(9, 1, 2)){
	elem match{
		case _ if (elem % 2 == 0) => println(elem + " is even.")
		case _ => println(elem + " is odd.")
	}
}

(4)case 類的匹配

case class Car(brand: String, price: Int)
val myBYDCar = new Car("BYD", 89000)
val myBMWCar = new Car("BMW", 120000)
val myBenzCar = new Car("Benz", 150000)
for (car <- Lisr(myBYDCar, myBMWCar, myBenzCar)) {
	car match {
		case Car(("BYD", 89000)) => println("")
		case Car((brand, price)) => println("Brand: " + brand + ",  Price:" + price)
	}
}

(5)Option型別
用case類來表示那種可能存在、也可能不存在的值
當預計到變數或者函數返回值可能不會參照任何值的時候,建議使用Option型別
當存在可以被參照的值的時候,返回結果會用Some(Option的子類)來包含這個值,不存在,則返回None
Option型別有個getOrElse("")方法,使被參照的值不存在時不再顯示None,而是顯示括號裡的提示資訊
Option[T]實際上是個容器,可以看作只包含一個元素或者不包含元素的集合

val books = Map("Hadoop" -> 1, "Spark" -> 2)
books.get("Hadoop")
=> Option[Int] = Some(1)
val sales = books.get("hive").foreach(println)
=> Option[Int] = None
sales.getOrElse("No Such Book")
=> No Such Book

10. 函數的型別和值

函數位面量:可以體現函數語言程式設計的核心理念
定義函數

def counter(val: Int): Int = {val += 1} 

函數型別 (Int) => Int <=> Int => Int
函數值:型別宣告部分去掉,剩下的就是函數的「值」 (val) => {val += 1}
定義函數和定義變數的拆解類比

名稱 型別
val counter: Int => Int = (val) => {val += 1} counter Int => Int (val) => {val += 1}
val num: Int = 5 num Int 5

11. 匿名函數: lambda表達式

(num: Int) => num * 2

閉包(還不懂):函數內部可以呼叫函數外部的一些變數值,每次呼叫都會建立一個新閉包

var more = 1
val addMore = (x: Int) => x + more

12. 佔位符語法:下劃線 _

13. 集合的操作

(1)遍歷

university foreach {case(k, v) => println(k + "+" + v)}	
<=>	university.foreach({case(k, v) => println(k + "+" + v)})
<=>	university foreach {kv => println(kv._1 + "+" + kv._2)}

(2)Map

val books = List("Hadoop", "Hive", "HDFS")
books.map(s => s.toUpperCase)	// map操作
=>	List("HADOOP", "HIVE", "HDFS")
books.flatMap(s => s.toList)	// flatMap操作
=>	List("H", "a",, "H", "I",, "H", "D",)

(3)filter

val uni = university filter {kv => kv._2 contains "scala"}

(4)reduce and fold

val list = List(1, 2, 3, 4, 5)
// reduce 歸約:reduceLeft / reduceRight
list.reduce(_ + _) = 15  // 從左往右加 => 1 + 2 + ... + 5
// fold:類似於reduce,只是需要個種子值
list.fold(10)(_ * _) = 1200	// 種子值 * 從左往右乘 => 10 * 1 * 2 * ... * 5