【Spark】Spark學習筆記

本文意在收集整理網絡上對Spark講解比較好的博文,希望能發揮一個“Spark字典”的作用。

先列出一個比較好的入門級Spark教程:廈門大學數據庫實驗室的Spark教程。

廈門大學Spark入門教程(Scala版)

Scala菜鳥教程


第一部分:Scala基礎

1. if語句

Scala中的if表達式的值可以直接賦值給變量。

val x = 6
val a = if (x>0) 1 else -1  // a的值爲1

2. for循環

Scala中的for循環語句格式如下,其中,“變量<-表達式”被稱爲“生成器(generator)”。這裏,i不需要提前進行變量聲明,可以在for語句括號中的表達式中直接使用。

for (i <- 1 to 5) println(i)  // for (變量<-表達式) 語句塊
for (i <- 1 to 5 by 2) println(i)  // 使用by來控制設置步長,這裏i取值爲1、3、5
for (i <- 1 to 5; j <- 1 to 3) println(i*j)  // for循環的多生成器,相當於雙重for循環

for循環中的yield關鍵字會把當前的元素記下來,保存在集合中,循環結束後將返回該集合。Scala中的yield的主要作用是記住每次迭代中的有關值,並逐一存入到一個數組中。這種帶有yield關鍵字的for循環,被稱爲“for推導式”。

scala> for (i <- 1 to 5) yield i
res: scala.collection.immutable.IndexedSeq[Int] = Vector(1, 2, 3, 4, 5)

3. 數組Array

val intArray = new Array[Int](3)  // 聲明一個長度爲3的整型數組,每個數組元素初始化爲0

在Scala中,對數組元素的應用,使用的是圓括號intArray(0),而不是方括號intArray[0],這一點和Java是不同的。

4. 列表List

val intList = List(1,2,3)  // 聲明一個列表

:: 方法被稱爲cons,意爲構造,具有右結合的特性,向隊列的頭部追加數據,創造一個新的列表。用法爲 x::list,其中x爲加入到頭部的元素,無論x是列表與否,它都只將成爲新生成列表的第一個元素,也就是說新生成的列表長度爲list的長度+1。

x::list等價於list.::(x)

Scala中,列表中各個元素必須是相同類型,Nil表示空列表

Scala可以使用 ::: 方法對不同的列表進行連接得到一個新的列表。

List的遍歷可以採用for循環的方式,也可以採用Java裏面foreach的方式:

// for循環遍歷列表
val list = List(1, 2, 3, 4, 5)
for (elem <- list) println(elem)

5. 元組Tuple

val tuple = ("scala", 2020, 2020.01)  // 聲明一個元組,只需要用圓括號把多個元組的元素括起來就可以了

元組是不同類型的值的聚集也就是說元組可以包含不同類型的元素。 

當需要訪問元組中的某個元素的值時,可以通過類似tuple._1、tuple._2、tuple._3這種方式實現。

6. 集合Set

var set = Set("Hadoop","Spark")  // 聲明一個集合,默認是不可變集合;聲明一個可變集,則需要引入scala.collection.mutable.Set包
set += "Scala"  // 向set中增加新的元素

集合Set是不重複元素的集合,集合中的元素是以hash的方式進行存儲的,方便快速地找到某個元素。

集合分爲可變集合和不可變集合。

想要在集合中進行插入刪除等操作,若聲明的是不可變集合,需使用var標識,此時的操作會產生一個新的集,原來的集並不會發生變化;若聲明的是可變集合,使用val標識即可,此時改變的是該集合本身。

7. 映射Map

val events = Map("A" -> "click", "B" -> "view","C"->"skip")  // 聲明一個Map
val value = events("A")  // 可以使用key來獲取Map中這個key對應的value

在Scala中的Map是一系列鍵值對的集合,以key-value的形式進行存儲。與Set類似,Scala中的Map包括可變和不可變兩種,默認情況下創建的是不可變Map,如果需要創建可變Map,需要引入scala.collection.mutable.Map包。

Map可以直接使用key來進行一些操作,如增加元素、更新已有key的值等。

import scala.collection.mutable.Map
val events = Map("A" -> "click", "B" -> "view","C"->"skip")
events ("A") = "click again"  // 更新已有key的值
events ("D") = "like"  // 向map中添加新的元素
// 等效方法
events + = ("D"->"like")  // 添加一個新元素
events + = ("D"->"like","E"->"dislike",F->"share")  // 同時添加多個新元素

Map的遍歷可以使用for循環的方式,也可以跟Java一樣,使用迭代器的方式:

for (x <- map) 語句塊  // 方式1,這裏獲取x的key/value可使用x._1/x._2
for ((k,v) <- map) 語句塊  // 方式2
map.iterator.foreach(x => 語句塊))  // 方式3

如Java一樣,Scala中的Map也支持只去Map的key的集合或者Map的value的集合。

events.keys
events.values

或者,Map的遍歷也可以採用foreach的方式:

// 等價操作
events foreach {case(k,v) => println(k+":"+v)}
events.foreach({case (k,v) => println(k+":"+v)})

8. 類和對象

Scala中的類和對象的使用與Java中的類和對象並沒有太多的本質區別,只是有些特別的地方需要注意一些。

Scala類的成員如果沒有使用訪問修飾符修飾,默認是public訪問類型,外部可以訪問該字段。

Scala中調用方法可以使用類似Java的對象調用的方式,Scala在調用無參方法時,可以省略方法名後面的圓括號,只給出方法名即可。

object.無參方法名() 等價於 object.無參方法名

Scala並沒有提供static這個關鍵字,Scala通過使用關鍵字object來實現類的單例模式。Scala中使用單例模式時,除了定義的類之外,還要定義一個同名的object 對象,它和類的區別是:object對象不能帶參數。

單例對象用關鍵字object定義,在Scala中,單例對象分爲兩種,一種是並未自動關聯到特定類上的單例對象,稱爲獨立對象另一種是關聯到一個類上的單例對象,該單例對象與該類共有相同名字,則這種單例對象稱爲伴生對象

當單例對象與某個類共享同一個名稱時,這個單例對象被稱作是這個類的伴生對象:companion object,類被稱爲是這個單例對象的伴生類:companion class。類和它的伴生對象必須定義在同一個源文件裏,類和它的伴生對象可以互相訪問其私有成員。

9. 方法與函數

Scala 使用 def 語句定義方法。方法是類的一部分,這一點跟 Java 的類似。

Scala方法的聲明格式:

def functionName ([參數列表]) : [return type]

Scala方法的定義格式:

def functionName ([參數列表]) : [return type] = {
   function body
   return [expr]
}

 

Scala中,如果方法沒有返回值,可以返回Unit,這個類似於Java的void同時,方法中的參數列表如果是多個參數,需要使用逗號進行分隔,並且每個參數都要指定數據類型。方法的返回值,不需要靠return語句,方法裏面的最後一個表達式的值就是該方法的返回值。比如,上面max()方法裏面x或者y就是該方法的返回值。

方法返回類型後面的等號和大括號後面,是該方法的方法體,包含了該方法要執行的具體操作語句。如果大括號裏面只有一行語句,那麼也可以直接去掉大括號。

def sum(x:Int, y:Int):Int = x+y

在Scala中允許對“字面量”直接執行方法,下面的兩種用法是等價的,前者是後者的簡寫形式。

a 方法 b
a.方法(b)

Scala中方法與函數在語義上的區別很小。Scala方法是類的一部分,而函數是一個對象可以賦值給一個變量。換句話來說在類中定義的函數即是方法。

在函數式編程中,函數的“類型”和“值”也成爲兩個分開的概念,函數的“值”,就是“函數字面量”。

def counter(value: Int): Int = { value += 1}  // 定義一個函數

 這個函數的“類型”爲:

(Int) => Int  // 函數類型,當參數只有一個時,圓括號可以省略,等價於:Int => Int

只要把函數定義中的類型聲明部分去除,剩下的就是函數的“值”

(value) => {value += 1}  // 只有一條語句時,大括號可以省略

 採用類似定義變量的方式定義一個函數:

// val 變量名:變量類型 = 變量的值
val num: Int = 5 
// val 函數名:函數的類型 = 函數的值
val counter: Int => Int = { (value) => value += 1 }

10. 特質trait

Scala中沒有接口的概念,而是通過特質(trait)來實現了接口的功能,如與接口不同的是,trait還可以定義屬性和方法的實現。。Scala中,一個類只能繼承自一個超類,卻可以實現多個特質,通過重用特質中的方法和字段,進而實現多重繼承。

trait的定義方式與class類似,使用的關鍵字trait進行定義,如下所示:

trait Equal {
  def isEqual(x: Any): Boolean
  def isNotEqual(x: Any): Boolean = !isEqual(x)
}

trait中沒有方法體的方法,默認是抽象方法。上面的代碼中,isEqual()方法沒有定義方法的實現,isNotEqual()定義了方法的實現。子類繼承trait可以實現未被實現的方法。所以其實Scala的trait更像是Java的抽象類。

11. 模式匹配

一個模式匹配包含了一系列備選項,每個都開始於關鍵字case。每個備選項都包含了一個模式及一到多個表達式。箭頭符號 => 隔開了模式和表達式。

Scala的模式匹配最常用於match語句中,match對應Java裏的switch,但是寫在選擇器表達式之後。match表達式通過以代碼編寫的先後次序嘗試每個模式來完成計算,只要發現有一個匹配的case,剩下的case不會繼續匹配。

  • 選擇器 match {備選項}
  • 備選項以關鍵字case開始
  • 每個備選項都包含了一個模式及一到多個表達式用箭頭符號=>分隔
def matchTest(x: Any): Any = x match {
      case 1 => "one"
      case "two" => 2
      case y: Int => "scala.Int"
      case _ => "many"  // 這個case表示默認的全匹配備選項,即沒有找到其他匹配時的匹配項,類似switch中的default
   }

“case _”表示默認的全匹配備選項,即沒有找到其他匹配時的匹配項,類似switch中的default。 

12. 樣例類case class

使用了case關鍵字的類定義就是就是樣例類(case class),樣例類是種特殊的類,經過優化以用於模式匹配。

object Test {
   def main(args: Array[String]) {
    val alice = new Person("Alice", 25)
    val bob = new Person("Bob", 32)
    val charlie = new Person("Charlie", 32)
   
    for (person <- List(alice, bob, charlie)) {
        person match {
            case Person("Alice", 25) => println("Hi Alice!")
            case Person("Bob", 32) => println("Hi Bob!")
            case Person(name, age) =>
               println("Age: " + age + " year, name: " + name + "?")
         }
      }
   }
   // 樣例類
   case class Person(name: String, age: Int)
}

// 輸出結果
Hi Alice!
Hi Bob!
Age: 32 year, name: Charlie?

Scala中的class類似於Java中的class,而case class被稱爲樣例類,是一種特殊的類,常被用於模式匹配。

case class的定義:

case class ClassName 參數列表(可爲空)

在Scala中的case class其實就是一個普通的class,但是它又和普通的class略有區別:

  • case class初始化的時候可以不用new,也可以加上,但是class必須加new;
  • 默認實現了equals、hashCode方法;
  • 默認是可以序列化的,也就是實現了Serializable;
  • 自動從scala.Product中繼承一些函數;
  • case class構造函數的參數是public的,可以直接訪問;
  • case class默認情況下不能修改屬性值;
  • case class最重要的功能是支持模式匹配,這也是定義case class的重要原因。

當一個類被聲名爲case class的時候,scala會幫助我們做下面幾件事情:

  1. 構造器中的參數如果不被聲明爲var的話,它默認的話是val類型的,但一般不推薦將構造器中的參數聲明爲var;
  2. 自動創建伴生對象,同時在裏面給我們實現子apply方法,使得我們在使用的時候可以不直接顯示地new對象(也就是說,在實例化樣例類時,不需要使用關鍵字new,這是因爲案例類有一個默認的apply方法來負責對象的創建);
  3. 伴生對象中同樣會幫我們實現unapply方法,從而可以將case class應用於模式匹配,關
  4. 實現自己的toString、hashCode、copy、equals方法

除此之此,case class與其它普通的scala類沒有區別。

補充一點:如果有一個class,還有一個與class同名的object,那麼就稱這個object是這個class的伴生對象,這個class是這個object的伴生類。

13. lambda表達式

lambda是可以理解爲一種匿名函數,可以採用匿名函數的定義形式,lambda表達式的形式:

(參數) => 表達式  // 如果參數只有一個,參數的圓括號可以省略

第二部分:Spark基礎 

1. map與flatMap

(1)一些比較好的講解

Scala入門:map操作和flatMap操作

spark RDD 的map與flatmap區別說明

(2)map操作

map(func):將每個元素傳遞到函數func中,並將結果返回爲一個新的數據集。

map操作是針對集合的典型變換操作,它將某個函數應用到集合中的每個元素,併產生一個結果集合。

換句話說,map是將集合中的每一個元素輸入映射爲一個新對象。{蘋果,梨子}.map(去皮)= {去皮蘋果,去皮梨子} ,其中: “去皮”函數的類型爲:A => B,是一種lamda表達式的形式。

(3)flatMap操作

flatMap(func):與map()相似,但每個輸入元素都可以映射到0或多個輸出結果。

flatMap是map的一種擴展。在flatMap中,我們會傳入一個函數,該函數對每個輸入都會返回一個集合(而不是一個元素),然後,flatMap把生成的多個集合“拍扁”成爲一個集合。

flatMap其實包含着兩個操作:首先會將每一個輸入對象輸入映射爲一個新集合,然後把這些新集合連成一個大集合。 {蘋果,梨子}.flatMap(切碎) = {蘋果碎片1,蘋果碎片2,梨子碎片1,梨子碎片2} ,其中: “切碎”函數的類型爲: A => List<B>,這個函數返回的必須是一個List。

簡單理解,flatMap與map唯一不一樣的地方就是傳入的函數在處理完後返回值必須是List,其實這也不難理解,既然是flatMap,那除了map以外必然還有flat的操作,所以需要返回值是List才能執行flat這一步。

這裏再次強調一下函數的等價用法:

val books = List("Hadoop","Hive","HDFS")
// 下面的操作是等價的
books flatMap (s => s.toList)
books.flatMap(s => s.toList)

2. reduceByKey與groupByKey

(1)一些比較好的講解

【Spark系列2】reduceByKey和groupByKey區別與用法

(2)reduceByKey

reduceByKey(func)的功能是,使用func函數合併具有相同鍵的值。將reduceByKey應用於(K,V)鍵值對的數據集時,返回一個新的(K, V)形式的數據集,其中的每個值是將每個key傳遞到函數func中進行聚合。

比如,reduceByKey((a,b) => a+b),有四個鍵值對(“spark”,1)、(“spark”,2)、(“hadoop”,3)和(“hadoop”,5),對具有相同key的鍵值對進行合併後的結果就是:(“spark”,3)、(“hadoop”,8)。可以看出,(a,b) => a+b這個Lamda表達式中,a和b都是指value。

(3)groupByKey

groupByKey()的功能是,對具有相同鍵的值進行分組,應用於(K,V)鍵值對的數據集時,返回一個新的(K, Iterable)形式的數據集。比如,對四個鍵值對(“spark”,1)、(“spark”,2)、(“hadoop”,3)和(“hadoop”,5),採用groupByKey()後得到的結果是:(“spark”,(1,2))和(“hadoop”,(3,5))。

3. filter

 filter(func):篩選出滿足函數func的元素,並返回一個新的數據集。

Scala中可以通過filter操作來實現從一個集合中獲取滿足指定條件的元素組成一個新的集合。通俗地講,filter操作是在當前集合中“篩選”出符合指定條件的元素。

4. reduce

Scala使用reduce這種二元操作對集合中的元素進行歸約,reduce實現的是對集合當中的元素進行歸約操作。
reduce包含reduceLeft和reduceRight兩種操作,其中reduceLeft從集合的頭部開始操作,reduceRight從集合的尾部開始操作。

scala> val list = List(1,2,3,4,5)
list: List[Int] = List(1, 2, 3, 4, 5)
scala> list.reduceLeft(_ + _)
res: Int = 15
scala> list.reduceRight(_ + _)
res: Int = 15

reduceLeft和reduceRight都是針對兩兩元素進行操作,在上面代碼中,reduceLeft(_ + _)表示從列表頭部開始,對兩兩元素進行求和操作,下劃線是佔位符,用來表示當前獲取的兩個元素,兩個下劃線之間的是操作符,表示對兩個元素進行的操作,這裏是加法操作(當然也可以使用其他操作)。 reduceRight(_ + _)表示從列表尾部開始,對兩兩元素進行求和操作。

如果直接使用reduce,而不用reduceLeft和reduceRigh,默認採用的是reduceLeft。

5. sortBy和sortByKey

(1)一些比較好的講解

sortBy是對標準的RDD進行排序,而sortByKey函數是對PairRDD進行排序,也就是有Key和Value的RDD。

Spark: sortBy和sortByKey函數詳解

(2)sortBy

sortBy可以指定對鍵還是value進行排序,返回根據提供的參數進行排序的RDD。該函數最多可以傳三個參數:
  第一個參數是一個函數,該函數的也有一個帶T泛型的參數,返回類型和RDD中元素的類型是一致的;
  第二個參數是ascending,這個參數決定排序後RDD中的元素是升序還是降序,默認是true,也就是說sortBy默認升序排序
  第三個參數是numPartitions,該參數決定排序後的RDD的分區個數,默認排序後的分區個數和排序之前的個數相等,即爲this.partitions.size。

(3)sortByKey

sortByKey函數作用於Key-Value形式的RDD,並對Key進行排序。返回一個根據鍵排序的RDD。

6. class與object

在scala中沒有靜態方法和靜態字段,所以在scala中可以用object來實現這些功能。object相當於class的單個實例,類似於Java中的static,通常在裏面放一些靜態的field和method。

第一次調用object中的方法時,會執行object的constructor,也就是object內部不在method或者代碼塊中的所有代碼,但是object不能定義接受參數的constructor 。object的構造函數只會在第一次被調用時被執行一次,類似於Java類中static的初始化。

Scala中如果一個Class和一個Object同名,則稱Class是Object的伴生類。Scala沒有Java的Static修飾符,Object下的成員和方法都是靜態的,類似於Java裏面加了Static修飾符的成員和方法。Class和Object都可以定義自己的Apply()方法,類名()調用Object下的Apply()方法,變量名()調用Class下的Apply()方法。在object中一般可以爲伴生類做一些初始化等操作。

類名()->調用了對應object下的apply方法
對象名()->調用了對應class的apply方法

還有一種解釋是,class是定義是描述。它依照方法、或者其他組成元素定義一個類型。object相當於class的單例,它是你所定義的類的唯一實例。每個代碼中的object都將創建一個匿名類,這個匿名類繼承了你聲明本object想要實現的類。

 

 

 

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章