1. 程式人生 > >Scala學習之路----基礎入門

Scala學習之路----基礎入門

語句 計算 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學習之路----基礎入門