Scala學習之路----基礎入門
阿新 • • 發佈:2018-07-19
語句 計算 sum 自動創建 lse 鍵值 name ctrl fir 一、Scala解釋器的使用
REPL:Read(取值)-> Evaluation(求值)-> Print(打印)-> Loop(循環)
scala解釋器也被稱為REPL,會快速編譯scala代碼為字節碼,然後交給JVM來執行。
計算表達式:在scala>命令行內,鍵入scala代碼,解釋器會直接返回結果。
如果你沒有指定變量來存放這個值,那麽值默認的名稱為res,而且會
顯示結果的數據類型,比如Int、Double、String等等。
例如,輸入1 + 1,會看到res0: Int = 2
內置變量:在後面可以繼續使用res這個變量,以及它存放的值。
例如,"Hi, " + res0,返回res2: String = Hi, 2
自動補全:在scala>命令行內,可以使用Tab鍵進行自動補全。
二、聲明變量
聲明val常量:可以聲明val常量來存放表達式的計算結果。
例如,val result = 1 + 1
但是常量聲明後,是無法改變它的值的,否則會返回error: reassignment
to val的錯誤信息。
聲明var變量:如果要聲明值可以改變的引用,可以使用var變量。
例如,var myresult = 1,myresult = 2
但是在Scala程序中,通常建議使用val,也就是常量。
因為在Spark的大型復雜系統中,需要大量的網絡傳輸數據,
如果使用var,值可能被錯誤的更改,所以建議多使用val。
三、數據類型與操作符
1》基本數據類型: Byte、 Char、 Short、 Int、 Long、 Float、 Double、 Boolean。
Scala的數據類型統一都是類。 Scala自己會負責基本數據類型和引用類型的轉換操作。
使用以上類型, 直接就可以調用大量的函數, 例如, 1.toString(), 1.to(10)。
類型的加強版類型: Scala使用很多加強類給數據類型增加了上百種增強的功能或函數。
·例如, String類通過StringOps類增強了大量的函數, "Hello".intersect(" World")。
·例如, Scala還提供了RichInt、 RichDouble、 RichChar等類型, RichInt就提供了to函數, 1.to(10), 此處Int先隱式轉換為RichInt,然後再調用其to函數。
2》基本操作符: Scala的算術操作符與Java的算術操作符也沒有什麽區別, 比如+、 -、 *、 /、 %等, 以及&、 |、 ^、 >>、 <<等。
但是, 在Scala中, 這些操作符其實是數據類型的函數, 比如1 + 1, 可以寫做1.+(1)
例如, 1.to(10), 又可以寫做1 to 10
註:Scala中沒有提供++、--操作符, 我們只能使用+和-, 比如counter = 1,counter++是錯誤的, 必須寫做counter += 1
3》除了方法之外,Scala還提供函數
數學函數:sqrt() pow() min()
引入特定包時使用import 包名._;
import scala.math._ ,_是通配符,類似Java中的*
四、流程控制結構
1、if表達式的定義: 在Scala中, if表達式是有值的, 就是if或者else中最後一行語句返回的值。
例如, val age = 30; if (age > 18) 1 else 0
可以將if表達式賦予一個變量, 例如, val isAdult = if (age > 18) 1 else 0
另外一種寫法, var isAdult = -1; if(age > 18) isAdult = 1 else isAdult = 0, 但是通常使用上一種寫法
2、if表達式的類型推斷: 由於if表達式是有值的, 而if和else子句的值類型可能不同, 此時if表達式的值是什麽類型呢?
Scala會自動進行推斷, 取兩個類型的公共父類型Any。
例如, if(age > 18) 1 else 0, 表達式的類型是Int, 因為1和0都是Int
例如, if(age > 18) "adult" else 0, 此時if和else的值分別是String和Int, 則表達式的值是Any, Any是String和Int的公共父類型。
如果if後面沒有跟else, 則默認else的值是Unit, 也用()表示, 類似於Java中的void或者null。
例如, val age = 12; if(age > 18) "adult"。 此時就相當於if(age > 18) "adult" else ()。
3、將if語句放在多行中: 默認情況下, REPL只能解釋一行語句, 但是if表達式通常需要放在多行。
可以使用{}的方式, 比如以下方式, 或者使用:paste和ctrl+D的方式。
if(age > 18) { "adult"
} else if(age > 12) "teenager" else "children"
註:默認情況下, Scala不需要語句終結符, 默認將每一行作為一個語句
4、一行放多條語句: 如果一行要放多條語句, 則必須使用語句終結符
例如, 使用分號作為語句終結符, var a, b, c = 0; if(a < 10) { b = b + 1;c = c + 1 }
通常來說, 對於多行語句, 還是會使用花括號的方式
var a, b, c = 0;if(a < 10) {
b = b + 1;c = c + 1}
5、塊表達式: 塊表達式, 指的就是{}中的值, 其中可以包含多條語句, 最後一個語句的值就
是塊表達式的返回值。
例如, var d = if(a < 10) { b = b + 1; c + 1 }
6、輸入和輸出
print和println: print打印時不會加換行符, 而println打印時會加一個換行符。
printf可以用於進行格式化,例如, printf("Hi, my name is %s, I‘m %d years old .", "Leo", 30)
readLine: readLine允許我們從控制臺讀取用戶輸入的數據, 類似於Java中的System.in和Scanner的作用。
綜合案例:使用paste和ctrl+D
val name = readLine("Welcome to Game House. Please tell me your name: ")
print("Thanks. Then please tell me your age: ")
val age = readInt()
if(age > 18) {
printf("Hi, %s, you are %d years old, so you are legel to come here!", name, age)
} else {
printf("Sorry, boy, %s, you are only %d years old. you are illegal to come here!",
name, age)
}
五、循環
1、Scala擁有與Java相同的while和do-while循環
但沒有與for(初始化變量;判斷條件;更新變量)循環直接對應的對構。
Scala中的for:for(i<-表達式),讓變量i遍歷<-右邊表達式的所有值。
註意:
1、在for循環的變量之前並沒有val或var的指定,該變量的類型是集合的元素類型。
2、循環變量的作用域一直持續到循環結束
3、to 和 until,兩者得到都是集合,區別如下:
scala> 1 to 10 (包含10)
res8: scala.collection.immutable.Range.Inclusive = Range(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
scala> 1 until 10 (不包含10)
res10: scala.collection.immutable.Range = Range(1, 2, 3, 4, 5, 6, 7, 8, 9)
4、循環的幾種遍歷方式
一、直接遍歷------遍歷字符串
二、求和---------1到8的和
三、以 變量<-表達式 的形式提供多個生成器,用分號將它們隔開(嵌套循環)
for(i<- 1 to 9;j<- 1 to i){
if(i==j) println(j+"*"+i+"="+j*i)
else print(j+"*"+i+"="+j*i+"\t")
}
四、在循環中使用變量
for(i<- 1 to 6;tem=2*i-1;j<- 1 to tem){
print("*");if(j==tem) {println()}}
五、守衛式,即在for循環中添加過濾條件if語句
for(i<- 1 to 3;j<- 1 to 3 if i!=j) print((10*i+j)+" ")
六、推導式
如果for循環的循環體以yield開始,則該循環會構造出一個集合,每次叠代生成集合中的一個值。
六、函數
1、函數的分類
單行函數:def sayHello(name: String) = print("Hello, " + name)
多行函數:如果函數體中有多行代碼, 則可以使用代碼塊的方式包裹多行代碼, 代碼塊中最後一行
的返回值就是整個函數的返回值。 與Java中不同, 不能使用return返回值。
比如如下的函數, 實現累加的功能:
def sum(n: Int) :Int= {
var sum = 0;
for(i <- 1 to n) sum += i
sum}
2、函數的定義與調用
在Scala中定義函數時, 需要定義函數的函數名、 參數、 函數體。
def sayHello(name: String, age: Int) = {
if (age > 18) { printf("hi %s, you are a big boy\n", name); age }
else { printf("hi %s, you are a little boy\n", name); age}}
調用:sayHello("leo", 30)
註:Scala要求必須給出所有參數的類型, 但是不一定給出函數返回值的類型。
只要右側的函數體中不包含遞歸的語句, Scala就可以自己根據右側的表達式推斷出返回類型。
3、遞歸函數與返回類型
如果在函數體內遞歸調用函數自身, 則必須給出函數的返回類型。
例如, 實現經典的斐波那契數列:
def feibo(n:Int):Int={
if(n<=2) 1
else feibo(n-1)+feibo(n-2)}
例如如下階乘:
4、參數
1》默認參數
在Scala中, 有時我們調用某些函數時, 不希望給出參數的具體值, 而希望使用參數自身默認的值, 此時就在定義函數時使用默認參數。
def sayHello(firstName: String, middleName: String = "William", lastName:
String = "Croft") = firstName + " " + middleName + " " + lastName
如果給出的參數不夠, 則會從左往右依次應用參數。
Java與Scala實現默認參數的區別如下:
-----------------------------------------------------------------------------------
Java:
public void sayHello(String name, int age) {
if(name == null) {
name = "defaultName"
} if(age == 0) {
age = 18
}}
sayHello(null, 0)
-----------------------------------------------------------------------------------
Scala:
def sayHello(name: String, age: Int = 20) {
print("Hello, " + name + ", your age is " + age)}
sayHello("leo")
2》帶名參數
在調用函數時, 也可以不按照函數定義的參數順序來傳遞參數, 而是使用帶名參數的方式來
傳遞。如:sayHello(firstName = "Mick", lastName = "Nina", middleName = "Jack")
還可以混合使用未命名參數和帶名參數, 但是未命名參數必須排在帶名參數前面。如下:
正確:sayHello("Mick", lastName = "Nina", middleName = "Jack")
錯誤:sayHello("Mick", firstName = "Nina", middleName = "Jack")
3》使用序列調用變長參數
在如果要將一個已有的序列直接調用變長參數函數, 是不對的。 比如val s = sum(1 to 5)。
此時需要使用Scala特殊的語法將參數定義為序列, 讓Scala解釋器能夠識別。
這種語法非常有用!Spark的源碼中大量地使用。
例如:val s = sum(1 to 5 : _*) 通過:_*轉換成參數序列
案例: 使用遞歸函數實現累加,如下:
def sum2(nums: Int*): Int = {
if (nums.length == 0) 0
else nums.head + sum2(nums.tail: _*)
}
調用:sum2(1,2,3,4,5) 或是 sum2(1 to 5 :_*)
註:1、定義nums為一個變長參數,定義函數時用*,調用函數需要表示一個參數序列時用:_*
2、head 表示集合中的第一個元素,tail 表示集合中除了第一個元素外的其他元素
七、過程
定義:在Scala中, 定義函數時, 如果函數體直接包裹在了花括號裏面, 而沒有使用=連接,
則函數的返回值類型就是Unit, 這樣的函數就被稱之為過程。過程通常用於不需要返回值的函數。
過程還有一種寫法, 就是將函數的返回值類型定義為Unit。比較如下:
def sayHello(name: String) = "Hello, " + name
def sayHello(name: String) { print("Hello, " + name); "Hello, " + name }
def sayHello(name: String): Unit = "Hello, " + name
八、lazy值
在Scala中, 提供了lazy值的特性, 也就是說, 如果將一個變量聲明為lazy, 則只有在第一次使用
該變量時, 變量對應的表達式才會發生計算。這種特性對於特別耗時的計算操作特別有用, 比如打開文件進行IO, 進行網絡IO等。
1、import scala.io.Source._
lazy val lines = fromFile("C://Users//Administrator//Desktop//test.txt").mkString
即使文件不存在, 也不會報錯, 只有第一次使用變量時會報錯, 證明了表達式計算的lazy特性
val lines = fromFile("C://Users//Administrator//Desktop//test.txt").mkString 這句會報錯
2、val lines=sc.textFile("file:///home/tg/datas/ws")
val rdd1=lines.flatMap(_.split(" ")).map((_,1)).reduceByKey(_+_)
val rdd2=rdd1.collect
算子: flatMap() map() reduceByKey()轉換類型的算子(transformation)
collect()行動類型的算子(action)
3、val rdd=lines.flatMap(_.split(" ")).map((_,1)).reduceByKey(_+_).map(m=>(m._2,m._1))
.sortByKey(true).map(m=>(m._2,m._1)).collect
註:轉換類型的算子就是lazy類型,當遇到action行動類型的算子時,才會觸發執行。
總結下劃線的用法:
1、導包時,導入包中所有內容 import scala.io.Source._
2、將數列(集合)轉換成參數序列 val result=sum(1 to 10:_*)
3、表示Spark算子操作的每一個元素 lines.flatMap(_.split(" ")).map((_,1)).reduceByKey(_+_)
八、異常
在Scala中, 異常處理和捕獲機制與Java是非常相似的。
try {
throw new IllegalArgumentException("x should not be negative")
} catch {
case _:IllegalArgumentException => println("Illegal Argument!")
} finally {
print("release resources!")
}
try {
throw new IOException("user defined exception")
} catch {
case e1:IllegalArgumentException => println("illegal argument")
case e2:IOException => println("io exception")
}
九、數組
若數組長度固定使用Array,若數組長度不固定則使用ArrayBuffer
1、定長數組的兩種創建方式
1》val nums=new Array[Int](10); //10個整數的數組 2》val array=Array("hello","jack")
省略關鍵字new創建數組的方式,實際上調用的是 Array.scala中的apply()方法。源碼如下:
def apply[T: ClassTag](xs: T*): Array[T] = {
val array = new Array[T](xs.length)
var i = 0
for (x <- xs.iterator) {
array(i) = x;
i += 1 }
array}
2、變長數組:數組緩沖
註:scala.collection.mutable._ 可變 scala.collection.immutable._ 不可變
對於那種長度按需要變化的數組,Java有ArrayList,Scala有ArrayBuffer
變長數組ArrayBuffer使用時要導包 import scala.collection.mutable.ArrayBuffer
var arr1=ArrayBuffer[Int]()
變長數組操作:1、arr1+=1 2、arr1+=(2,5,6) 3、arr1 ++=Array(3,4)
4、arr1.trimEnd(5) 返回值為空,需再次調用arr1來查看刪除後的數據
5、arr1.insert(1,3,4) 指定1的位置添加3,4元素
6、arr1.remove(3)
7、arr1.remove(3,2) 指定位置刪除指定數量的元素
3、變長數組與定長數組之間的轉換
變長數組→定長數組:.toArray (不改變原來的數組,系統會自動創建一個新的arry)
定長數組→變長數組:.toBuffer
4、遍歷數組
until是RichInt類的方法, 返回所有小於( 不包括) 上限的數字。
5、數組常用算法
,除了sum求和,max最大值,min最小值以外還有如下:
6、數組的quickSort()快速排序方法scala.util.Sorting.quickSort(array)
十、高階函數
filter :把一個函數作為參數的函數
for(i <- 0 until arr1.length if(arr1(i)%2==0)) print(arr1(i)+" ")
array.filter(m=>m%2==0) 這裏的m可以省去簡寫成 array.filter(_%2==0)
m=>m%2==0 匿名函數 m=>{m%2==0}
算子: spark中的算子有一部分是和scala中的高階函數是一致的,
但是有一部分是scala中沒有的,比如reducekey
簡寫用下劃線代替m: val rdd=lines.flatMap(_.split(" ")).map((_,1)).reduceByKey(_+_)
lines.flatMap(m=>m.split(" ")).map(word=>(word,1)).reduceByKey((x,y)=>x+y)
十一、映射(Map)
1、Scala映射就是鍵值對的集合Map。默認情況下,Scala中使用不可變的映射。
如果想使用可變集合Map,必須導入scala.collection.mutable.Map
不可變:
val map=Map("tom"->20,"jack"->23,"marray"->22)
val map2=Map(("tom",20),("jack",23),("marray",22))
可變:
val map3=scala.collection.mutable.Map(("tom",20),("jack",23),("marray",22))
val map4=new scala.collection.mutable.HashMap[String,Int]
映射這種數據結構是一種將鍵映射到值的函數。 區別在於通常的函數計算值, 而映射只是做查詢。
2、獲取映射中的值
註:1》如果映射並不包含請求中使用的鍵, 則會拋出異常。
2》要檢查映射中是否有某個指定的鍵, 可以用contains方法。
3》getOrElse方法, 若包含相應的鍵, 就返回這個鍵所對應的值, 否則返加0。
4》映射.get(鍵)這樣的調用返回一個Option對象, 要麽是Some(鍵對應的值), 要麽是None。
3、修改Map的元素
更新可變Map集合:
1》更新Map的元素 ages(www.quyingyulecs.com "Leo") = 31
2》增加多個元素 ages += ("Mike" -> 35, "Tom" -> 40)
3》 移除元素 ages -= "Mike"
更新不可變Map集合:
1》 添加不可變Map的元素, 產生一個新的集合Map, 原Map不變
val ages2 = ages + ("Mike" -> 36, "Tom" -> 40)
2》移除不可變Map的元素, 產生一個新的集合Map, 原Map不變
val ages3 = ages - "Tom"
4、遍歷Map操作
//遍歷map的entrySet for ((key, value) <- ages) println(key + " " + value)
// 遍歷map的key for (key <- ages.keySet) println(key)
// 遍歷map的value for (value <- ages.values) println(value)
// 生成新map, 反轉key和value for ((key, value) <- ages) yield (value, key)
5、SortedMap和LinkedHashMap
// SortedMap可以自動對Map的key的排序
val ages = scala.collection.immutable.SortedMap("leo" -> 30, "alice" -> 15, "jen" -> 25)
// LinkedHashMap可以記住插入entry的順序
val ages = new scala.collection.www.dasheng178.com/ mutable.LinkedHashMap[String, Int]
ages("leo") = 30
ages("alice") = 15
ages("jen") = 25
6、Java Map與Scala Map的隱式轉換
import scala.collection.JavaConversions.mapAsScalaMap
val javaScores = new java.util.HashMap[String, Int]()
javaScores.put("Alice", 10)
javaScores.put("Bob"www.mhylpt.com, 3)
javaScores.put("Cindy", 8)
val scalaScores: scala.collection.mutable.Map[String, Int] = javaScores
===========================================================
import scala.collection.JavaConversions.mapAsJavaMap
import java.awt.font.TextAttribute._
val scalaAttrMap = Map(FAMILY -> "Serif", SIZE -> 12)
val font = new java.awt.Font(scalaAttrMap)
7、元組(tuple)
概念:元組是不同類型的值的聚集,對偶是元組的最簡單形態,元組的索引從1開始,而不是0
Tuple拉鏈操作:
Tuple拉鏈操作指的就是zip操作,zip操作是Array類的方法, 用於將兩個Array, 合並為一個Array
比如 Array(v1)和Array(v2), 使用zip操作合並後的格式為Array((v1,v2)),合並後的Array的元素類型為Tuple。例子如下:
val students = Array("Leo", "Jack", "Jen")
val scores = Array(80, 100, 90)
val studentScores = students.zip(scores)
for ((student, score) <- studentScores) println(student + " " + score)
註:如果Array的元素類型是Tuple, 調用Array的toMap方法, 可以將Array轉換為Map
如,studentScores.toMap
Scala學習之路----基礎入門