主頁 >  其他 > Scala基礎(三) 模式匹配、樣例類與Actor編程

Scala基礎(三) 模式匹配、樣例類與Actor編程

2021-06-13 07:01:14 其他

Scala基礎(三) 模式匹配、樣例類與Actor編程

  • 模式匹配
    • 內容匹配
    • 資料型別匹配
    • 陣列與集合匹配
    • 宣告變數時匹配
  • 樣例類
  • 補充
    • apply方法
    • unapply方法
    • Option型別
    • 偏函式Partial Function
    • 正則
    • Scala例外處理
    • 泛型
  • Scala Actor并發編程
    • 準備作業
    • 并發編程對比
      • Java的并發編程
      • Scala的并發編程
    • 簡單使用
      • 死回圈收發訊息
      • React收發訊息
      • Actor的3種訊息發送方式
      • Actor并發WordCount

Scala語法

Scala函式式編程與面向物件編程

模式匹配

之前有寫過Scala中也可以模擬出JavaC#switch/case功能,

內容匹配

package com.aa.matchDEMO

import scala.io.StdIn

object MatchCase1 {
  def main(args: Array[String]): Unit = {
    
    //從控制臺stdin(標準輸入)接收用戶輸入的內容,根據內容進行模式匹配
    val result: String = StdIn.readLine()       // 死等,這是阻塞方法

    result match {
      case "hadoop" => println("hadoop>>>")
      case "spark" => println("spark>>>")
      case "flink" => println("flink>>>")
      case _=>println("*********hehe*********")
    }
  }
}

多運行幾次:

flink
flink>>>
hadoop
hadoop>>>
哈哈
*********hehe*********
spark
spark>>>

Process finished with exit code 0

資料型別匹配

package com.aa.matchDEMO

import scala.util.Random

object MatchCase2 {
  def main(args: Array[String]): Unit = {
    val array = Array(11, 22, "hello", "bye", true, false, 3.14, 6.33)

    val result: Any = array(Random.nextInt(array.length))		//隨機抽取陣列元素

    result match {
      //case x: Int  => println(x)
      case x: Int if x > 15 => println(x)     				 //匹配時+守衛條件,提前過濾
      case y: String => println(y)
      case z: Double => println(z)
      case w: Boolean => println(w)
      case _ => println("-----呵呵-----")      //不滿足上方的任一條件就會落入這里,相當于default
    }
  }
}

隨機獲取元素并進行型別匹配,使用守衛條件在隨機到11時會落入最后的_中,

陣列與集合匹配

package com.aa.matchDEMO

import scala.util.Random

//匹配陣列、集合
object MatchCase3 {
  def main(args: Array[String]): Unit = {
    var array = Array(1, 3, 5)
    array match {
      case Array(1, x, y) => println(x + " " + y)
      case Array(1, 3, 5) => println("精準匹配")
      case Array(1, _*) => println("1...")
      case _ => println("else")
    }

    //val list = List(0)
    //val list = List(0, "a", 2)
    val list = List(0, "a")
    //val list = List(0, "a")
    //val list = List(1,"a",1)
    list match {
      case 0 :: Nil => println("只有0的串列")
      //case 0::_ =>println("0開頭的串列")
      case x :: y :: Nil => println(s"只有2個元素${x},${y}的串列")
      case _ => println("else串列")
    }
  }
}

宣告變數時匹配

package com.aa.matchDEMO

//變數宣告時進行模式匹配
object MatchCase4 {
  def main(args: Array[String]): Unit = {
    val result: Array[Int] = (1 to 5).toArray

    val Array(_,x,y,_*) = result      //變數宣告時進行模式匹配
    println(s"x=${x},y=${y},x+y=${x+y}")  
  }
}
x=2,y=3,x+y=5

Process finished with exit code 0

在宣告變數時可以直接進行模式匹配,將匹配的結果作為新宣告變數的初值,

樣例類

樣例類是Scala中的特殊類(case修飾的),用于封裝資料,參與模式匹配、傳遞資料,顯然樣例類有2種:

  • case class:多例,需要構造器和引數串列,
  • case object:單例,不需要構造器(也就不需要引數串列),

類+case變成樣例類后底層發生了改變:

  • 樣例類自動實作了apply方法,不需要new,
  • 樣例類自動實作了toString方法,不再是列印記憶體地址,而是直接輸出屬性,
  • 樣例類自動實作了hashcodeequals方法,物件實體不再是比較記憶體地址,而是直接比較內容,

舉個栗子:

package com.aa.caseDEMO

object Case1 {
  def main(args: Array[String]): Unit = {
    val cat1 = new Cat("哈哈") //普通類需要先new才能創建實體物件
    val cat2 = new Cat("哈哈")

    println(cat1) //輸出com.aa.caseDEMO.Cat@4cdbe50f  普通類直接輸出的是記憶體地址
    println(cat1 == cat2) //輸出false 直接比較的是記憶體地址

    val dog1 = new Dog("呵呵") //idea自動提示new是多余的,new關鍵字發灰說明可以不寫
    val dog2 = Dog("呵呵")//不寫new也可以創建實體物件
    println(dog1) //Dog(呵呵) 說明自動寫好了toString
    println(dog1 == dog2) //true 說明自動寫好了equals和hashCode方法
  }
}

class Cat(var name: String) {
  //override def toString: String = super.toString    默認的toString方法
  override def toString: String = s"Cat[$name]"

  //override def equals(obj: Any): Boolean = super.equals(obj)  默認的equals方法
  def canEqual(other: Any): Boolean = other.isInstanceOf[Cat]

  override def equals(that: Any): Boolean = that match {
    case that: Cat => (that canEqual this) && name == that.name //是同一類的實體物件才能比較物件的內容
    case _ => false
  }

  override def hashCode(): Int = { //重寫hashCode方法
    //先比較hashCode,如果不同肯定內容不同,如果hashCode相同內容可能不同,再去equals比較內容,速度快
    val state = Seq(name)
    state.map(_.hashCode).foldLeft(0)((a, b) => 31 * a + b) //隨便瞎寫的演算法
  }
}

//樣例類
case class Dog(var name: String) {}

樣例類的核心功能是封裝資料+傳輸資料+模式匹配

package com.aa.caseDEMO

import scala.util.Random

object Case2 {
  def main(args: Array[String]): Unit = {
    val array1: Array[Object] = Array(new Student("李四"), Teacher("王五"), Teacher("馬六"), Zhangsan)
    val array2: Array[Object] = Array(Student("李四"), Teacher("王五"), Teacher("馬六"), Zhangsan)

    val result: Object = array2(Random.nextInt(array2.length)) //Object是Java的Object,頂級父類

    result match {
      case Teacher(name) => println(name) //直接提取屬性
      case Student(name) => println(name)
      case s: Student => println(s.name) //先匹配物件,在呼叫物件.屬性
      case Zhangsan => println("這貨是張三")
      case _ => println("其它情況")
    }
  }
}

class Student(var name: String)

case class Teacher(var name: String)

case object Zhangsan

object Student { //伴生物件內定義apply方法和unapply方法
  def apply(name: String): Student = new Student(name)

  def unapply(s: Student): Option[String] = {
    Some(s.name)
  }
}

補充

apply方法

package com.aa.others

//測驗apply方法
object ApplyCase {
  def main(args: Array[String]): Unit = {
    val array1 = new Array[Int](10)       //沒有初始值,需要new

    val array2:Array[Int] = Array(11,22,33)    //有初始值,不需要new
  }
}

之前在命令列就發現了創建集合時,有初始值就可以不用new,嘗試定位:

在這里插入圖片描述

Array.class第27行看到:

  def apply(x : scala.Int, xs : scala.Int*) : scala.Array[scala.Int] = { /* compiled code */ }

Apply其實也是一種構造方法,在創建物件時,如果不+new,編譯器就會嘗試尋找是否有apply方法,如果有就呼叫apply方法實作物件的創建,

package com.aa.others

//測驗apply方法創建類的實體物件
object ApplyCase1 {
  def main(args: Array[String]): Unit = {
    val student = Student //object是全域唯一的實體(靜態加載,單例)

    val zhangsan = new Person("張三")//普通類創建實體物件時必須new
    val lisi:Person = Person("李四")//呼叫apply方法完成new
  }
}

object Student //object是靜態加載的單例

class Person(var name: String)

object Person { //apply方法必須定義在類的伴生物件中
  //idea自動提示并補全apply方法
  def apply(name: String): Person = new Person(name)
}

在類的伴生物件中定義apply方法后,就可以直接呼叫伴生物件apply方法,不用new,

unapply方法

從字面上也知道先得有apply方法,才能有unapply方法:

object Student{  
def unapply(s: unapplyStudent): Option[String] = {
    Some(s.name)}
}

case Student(name) => println(name)

unapply方法中可以使用Some方法,定位到Some.class

package scala
@scala.SerialVersionUID(value = 1234815782226070388)
final case class Some[+A](val x : A) extends scala.Option[A] with scala.Product with scala.Serializable {
  def isEmpty : scala.Boolean = { /* compiled code */ }
  def get : A = { /* compiled code */ }
}

可以看到Some內部有get方法,其實unapply方法就是從物件中提取屬性,相當于提取器,

Option型別

量子疊加型別,表示可有可無的2種狀態,分為2個類:有(Some)、無(None),

package com.aa.others

object OptionCase {
  def main(args: Array[String]): Unit = {
    val map1 = Map("name"->"張三","age"->"13")

    //根據Key去Map中取值,有值顯式,無值為None
    val result1:Option[String] = map1.get("name")
    val result2:Option[String] = map1.get("age1")

    println(result1)      //Some(張三)  有值
    println(result2)      //None
  }
}

再看看Option型別多么“薛定諤”:

package com.aa.others

object OptionCase1 {
  def main(args: Array[String]): Unit = {
    def method1(x: Int, y: Int) = x / y

    println(method1(5, 2))
    //println(method1(5, 0))  會報錯
    /*
    Exception in thread "main" java.lang.ArithmeticException: / by zero
	at com.aa.others.OptionCase1$.method1$1(OptionCase1.scala:5)
	at com.aa.others.OptionCase1$.main(OptionCase1.scala:6)
	at com.aa.others.OptionCase1.main(OptionCase1.scala)

Process finished with exit code 1
     */

    def method2(x: Int, y: Int): Option[Int] = {
      if (y != 0) Some(x/y)
      else None
    }

    val result = method2(5, 3)
    println(result)
    val maybeInt = method2(5, 0)
    
    maybeInt match {
      case Some(s) =>println(s)
      case None =>println("除數=0,出錯")
    }
  }
}

偏函式Partial Function

這個“偏”類似“偏導數”的“偏”,Partial Function字面意思是部分的、不完全的函式,

表面上沒有match,實際上卻有一組case陳述句:

偏函式是Partial Function[A,B]的一個實體,A代表輸入引數型別,B代表回傳結果型別,

舉個栗子:

package com.aa.others

object PF1 {
  def main(args: Array[String]): Unit = {
    val array1: Array[Int] = Array(11, 22, 33)
    //println(array1.map(_*10))//[I@48140564  需要轉化才能列印
    println(array1.map(_ * 10).toBuffer) //ArrayBuffer(110, 220, 330)

    val array2: Array[Any] = Array(11, 22, "哈哈", 33)
    //println(array2.map(_*10)) 為了保證安全,默認不允許這么做
    println(array2.filter(_.isInstanceOf[Int])//先按資料型別進行合法性過濾
      .map(_.asInstanceOf[Int])//鏈式編程,對合法資料進行型別轉化
      .map(_*10).toBuffer)

    val pf1:PartialFunction[Any,Int]={
      case i:Int=>i*10
    }
    println(array2.collect(pf1).toBuffer)//collect方法接收偏函式作為引數
  }
}

可以看出偏函式相比鏈式編程的過濾→型別轉換→函式運算的三步走,只需要定義輸入、輸出型別與函式邏輯就可以直接出結果,還是很簡潔的,

package com.aa.others

object PF2 {
  def main(args: Array[String]): Unit = {
    def method1(i:Int) = i match {
      case 1 => println("一")
      case 2 => println("二")
      case 3 => println("三")
    }
    method1(2)

    val pf2:PartialFunction[Int,String] ={
      case 1 =>"一"
      case 2 =>"二"
      case 3 =>"三"
    }
    println(pf2(1))
  }
}

使用偏函式進行模式匹配和運算顯然也很簡潔,

正則

正則(學名Regular Expression),又名規則運算式,用于對字串內容進行匹配,在文本處理、上位機資料提取、ETL等操作中都需要正則,Scala中的正則類就叫RegEx類,直接new即可使用,當然也可以String.rRegEx類

package com.aa.others

import scala.util.matching.Regex

object RegExTest {
  def main(args: Array[String]): Unit = {
    val email1 = "1009057838@QQ.com"
    val email2 = "haha@com"

    //正常情況使用三對雙引號可以跨行
    //正則不行(出現了多余的標記)
//    val regex: Regex =
//    """
//      |.+@.+\..+
//      |""".r

    /*結果不正確
    List(, , , , , , , , , , , , , , , , , )
    List(, , , , , , , , )
     */

    val regex: Regex =
      """.+@.+\..+""".r
    println(regex.findAllMatchIn(email1).toList)
    println(regex.findAllMatchIn(email2).toList)
    /*
    List(1009057838@QQ.com)
    List()
     */

  }
}

Scala例外處理

和Java差不多,也可以使用idea的ctrl+art+t來抽取并封裝到try/catch/finally中:

package com.aa.others

object ExceptionCase1 {
  def main(args: Array[String]): Unit = {
    try {//idea使用ctrl+art+t抽取并封裝在try中
      val error = 5 / 0
      /*常見的分母0報錯
       Exception in thread "main" java.lang.ArithmeticException: / by zero
       at com.aa.others.ExceptionCase1$.main(ExceptionCase1.scala:5)
       at com.aa.others.ExceptionCase1.main(ExceptionCase1.scala)
      */
    } catch {//捕獲例外后對例外進行處理
      case e1:ArithmeticException => println("數值例外"+e1.getMessage)
      case e2:NullPointerException => println("空指標例外")
    } finally {//無論如何都會執行
      println("一定會執行")
    }

  }
}

當然偷懶的辦法是throw拋例外:

package com.aa.others

object ExceptionCase2 {
  def main(args: Array[String]): Unit = {
    println("開始執行")

    throw new Exception("拋出例外")
    
    /*執行后出現
    開始執行
    Exception in thread "main" java.lang.Exception: 拋出例外
	  at com.aa.others.ExceptionCase2$.main(ExceptionCase2.scala:7)
	  at com.aa.others.ExceptionCase2.main(ExceptionCase2.scala)

    Process finished with exit code 1
     */

    println("永遠不會執行")//idea中變灰,說明是無效代碼,沒有執行機會
  }
}

但是Scala不需要在方法上拋例外,,,

泛型

泛型是引數化型別, 泛型程式設計(generic programming),Scala中的泛型在使用時,通常用26個英文字母表示,常用T、A、B,套路一般是:

def 方法名[泛型名稱](..) = {
//...
}

舉個栗子:

package com.aa.genDEMO;

import java.util.ArrayList;

public class TestA {
    public static void main(String[] args) {
        Student1 student1 = new Student1();
        Person1 person1 = new Student1();//類的多型性,向上轉型

        ArrayList<Student1> arrayList1 = new ArrayList<Student1>();
        //ArrayList<Person1> arrayList2 = new ArrayList<Student1>();
        //Java中類的繼承關系不能延伸到集合,非變
    }
}

//定義Java類和繼承類
class Person1{}
class Student1 extends Person1{}

會報錯:

在這里插入圖片描述

因為Java支持的是非變

package com.aa.genDEMO

object TestGen{
  def main(args: Array[String]): Unit = {
    val array1 =new Array[String](4)    //T箭頭String
    val array2 = new Array[Int](2)    //T→Int

    //需要定義多個方法,,,很麻煩
    def method1(x:Array[String]) = x(x.length/2)
    def method2(x:Array[Int]) = x(x.length/2)
    println(method1(Array("11","哈哈","呵呵","嗷嗷嗷")))
    println(method2(Array(11,22,33)))

    //使用泛型,指代一個引數,泛指多數型別,只需要定義一個方法
    def method3[A](x:Array[A])=x(x.length/2)
    println(method3(Array("11","哈哈","呵呵","嗷嗷嗷")))
    println(method3(Array(11,22,33)))
    println(method3(Array(11.11,22.22,33.33)))
  }
}

還可以定義泛型類:

package com.aa.genDEMO

object TestGen1 {
  def main(args: Array[String]): Unit = {
    val cat1:Cat1 = Cat1("哈哈")
    //val cat2 = Cat1(11)//報錯Type mismatch

    val cat3 = Cat2("呵呵")
    val cat4 = Cat2(14)
    val cat5 = Cat2(true)

  }
}

case class Cat1(var name:String)
case class Cat2[T](var name:T)//定義泛型類

泛型還有上下界的問題:

[T <: 型別]    T <=型別   上界 T型別必須是后面指定的型別及其子類  

[T >: 型別]    T >=型別    下界 T型別必須是后面值的型別及其父類

如果類既有上界、又有下界,下界寫在前面,上界寫在后面

泛型還存在泛型延伸問題(Scala除了支持Java支持的非變外,還支持協變逆變):

class Super
class Sub extends Super

class Temp1[T]  //非變 
class Temp2[+T] //保持一致 繼續延伸
class Temp3[-T] //逆轉過來 延伸

def main(args: Array[String]): Unit = {
	val a:Temp1[Sub] = new Temp1[Sub]
	// 編譯報錯
	// 非變
	//val b:Temp1[Super] = a
    
	// 協變
	val c: Temp2[Sub] = new Temp2[Sub]
	val d: Temp2[Super] = c
	
    // 逆變
	val e: Temp3[Super] = new Temp3[Super]
	val f: Temp3[Sub] = e
}

Scala Actor并發編程

準備作業

如果導包時沒有需要的類別庫:

在這里插入圖片描述

這是∵類別庫路徑有誤(安裝Scala時默認有該類),需要在idea的左上角點file→Project Structure→Global Libraries中設定Scala SDK的路徑:

在這里插入圖片描述

這里點+號,選擇安裝路徑的lib包,確認后即可導包,

并發編程對比

Java的并發編程

模型是基于多執行緒+共享資料:執行緒越多,并發能力越強,當然不易維護,容易產生死鎖,

package com.aa.lock;


//jps -l 查看java行程 jstack + 行程號 查看死鎖的情況

public class DeadLock {
    private  Object lock1 = new Object();
    private  Object lock2 = new Object();

    public  void method1() throws InterruptedException {
        synchronized(lock1){
            System.out.println(Thread.currentThread().getName() + "獲取到lock1,請求獲取lock2....");
            Thread.sleep(1000);
            synchronized (lock2){
                System.out.println("獲取到lock2....");
            }
        }
    }
    public  void method2() throws InterruptedException {
        synchronized(lock2){
            System.out.println(Thread.currentThread().getName() + "獲取到lock2,請求獲取lock1....");
            Thread.sleep(1000);
            synchronized (lock1){
                System.out.println("獲取到lock1....");
            }
        }
    }

    public static void main(String[] args) {
        DeadLock deadLock = new DeadLock();

        new Thread(()-> {
            try {
                deadLock.method1();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }).start();
        new Thread(()-> {
            try {
                deadLock.method2();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }).start();

    }
}

Scala的并發編程

模型是基于actor事件+訊息發送和接收:actor越多并發能力越強,由于不共享資料,當然不存在鎖的問題,

簡單使用

package com.aa.actorDEMO

import scala.actors.Actor

class Actor1 extends Actor{
  override def act(): Unit = {
    for(i <- 1 to 10){
      println("Actor1:"+i)
      Thread.sleep(2000)
    }
  }
}
class Actor2 extends Actor{
  override def act(): Unit = {
    for(i <- 1 to 10){
      println("Actor2:"+i)
      Thread.sleep(2000)
    }
  }
}

object Test1{
  def main(args: Array[String]): Unit = {
    //創建actor實體
    val actor = new Actor1
    val actor2 = new Actor2
    //啟動actor
    actor.start()
    actor2.start()
  }
}

執行后看起來還是很像多執行緒的:

Actor2:1
Actor1:1
Actor1:2
Actor2:2
Actor1:3
Actor2:3

Process finished with exit code -1

死回圈收發訊息

Scala容易報錯,為了防止不必要的麻煩,盡量不同名(不使用class Actor2):

package com.aa.actorDEMO

import scala.actors.Actor
class Actor3 extends Actor{
  override def act(): Unit = {
    while (true){
      receive{
        case "hello"  => println("\\(@^0^@)/")
        case "bye" => println("ヾ(?ω?`)o")
        case "stop" => System.exit(0)
      }
    }
  }
}

object Actor3{
  def main(args: Array[String]): Unit = {
    val actor = new Actor3
    actor.start()
    //自己給自己發送訊息
    actor!"hello"
    actor!"hello"
    actor!"hello"
    actor!"bye"
    actor!"stop"
    actor!"hello"
  }
}
\(@^0^@)/
\(@^0^@)/
\(@^0^@)/(?ω?`)o

Process finished with exit code 0

其中:

標記說明
!發送異步訊息,沒有回傳值
!?發送同步訊息,等待回傳值
!!發送異步訊息,回傳值是Future[Any]

while(true)是阻塞式,效率低下,也不能復用執行緒,

在這里插入圖片描述

由于構造了伴生關系,idea左側的圖表都變了,,,很智能的玩意兒,,,

React收發訊息

react方法配合loop可以復用執行緒,更高效:

package com.aa.actorDEMO

import scala.actors.Actor
class Actor4 extends Actor{
  override def act(): Unit = {
    loop{
      react{
        case "hello"  => println("\\(@^0^@)/")
        case "bye" => println("ヾ(?ω?`)o")
        case "stop" => System.exit(0)
      }
    }
  }
}



object Actor4{
  def main(args: Array[String]): Unit = {
    val actor = new Actor4
    actor.start()
    //自己給自己發送訊息
    actor!"hello"
    actor!"hello"
    actor!"hello"
    actor!"bye"
    actor!"stop"
    actor!"hello"
  }
}

Actor的3種訊息發送方式

package com.aa.actorDEMO

import scala.actors.Actor
import scala.actors.Future

case class AsyncMessage(id: Int, msg: String)

case class Reply(msg: String)

case class SyncMessage(id: Int, msg: String)

class Actor5 extends Actor {
  override def act(): Unit = {
    loop {
      react {
        case AsyncMessage(id, msg) => { //異步
          println(s"id:${id},msg:${msg}")

          sender ! Reply("回傳訊息")
        }
        case SyncMessage(id, msg) => { //同步
          println(s"id:${id},msg:${msg}")
        }
      }
    }
  }
}

object Actor5 {
  def main(args: Array[String]): Unit = {
    val actor = new Actor5
    actor.start()

    //Actor有3種發送訊息的方式
    actor ! AsyncMessage(1, "異步發送,不需要回傳值")

    actor !? SyncMessage(2, "同步發送,阻塞等待回傳值")

    val future: Future[Any] = actor !! AsyncMessage(3, "異步發送,需要回傳值")
    val result: Reply = future.apply().asInstanceOf[Reply]
    println(result)

  }
}

同步發送一定會阻塞和死等,由于是單執行緒運行,直接卡在這一步:

id:1,msg:異步發送,不需要回傳值
id:2,msg:同步發送,阻塞等待回傳值

暫時屏蔽同步發送:

id:1,msg:異步發送,不需要回傳值
id:3,msg:異步發送,需要回傳值
Reply(回傳訊息)

顯然異步可以介紹到訊息,,,

Actor并發WordCount

比起之前在MapReduce中寫的WordCount還是簡單不少的:

原理是將分散在多個檔案的資料,利用actor異步處理,將回傳值先置于futureSet中,再運算到resultList中,最后整合到FinalResult中,

package com.aa.actorDEMO

import scala.actors.{Actor, Future}
import scala.collection.mutable
import scala.collection.mutable.ListBuffer
import scala.io.Source


//定義個樣例類 用于封裝提交的任務
case class SubmitTask(filePath: String)

//定義樣例類 用于封裝task處理的結果
case class ReplyResult(result: Map[String, Int])


class Task extends Actor {
  override def act(): Unit = {
    loop {
      react {
        //接收的task任務 處理資料 進行wc
        case SubmitTask(filePath) => {
          //從檔案中讀取資料 并且讀取的資料轉換為String,底層是全文讀取
          val content: String = Source.fromFile(filePath).mkString
          //將讀取的內容根據換行符切割成為一行行內容,\r\n就是分隔符
          val lines: Array[String] = content.split("\r\n")
          //根據分隔符進行單詞切割,并扁平化操作(將零散的單詞壓入一個集合中)
          val words: Array[String] = lines.flatMap(line => line.split(" "))
          //將單詞標記為鍵值對,單詞→(單詞,1) 的對偶元組(二元組)
          val wordAndOne: Array[(String, Int)] = words.map(word => (word, 1))
          //將單詞相同的分為一組
          val wordGrouped: Map[String, Array[(String, Int)]] = wordAndOne.groupBy(tuple2 => tuple2._1)
          //scala集合中有個方法mapValues,將value位置的map處理為新的value,并將處理的結果和之前的key組成一個新的map
          val result: Map[String, Int] = wordGrouped.mapValues(v => v.length)
          //for( (k,v)<- result) println(s"${k}---->${v}")

          //task處理完的結果進行回傳
          sender ! ReplyResult(result)
        }
      }
    }
  }
}

object WordCount {
  def main(args: Array[String]): Unit = {

    //創建一個集合 用于保存每個任務Future
    val futureSet = new mutable.HashSet[Future[Any]]()
    //創建一個集合 用于保存最終的每個task處理結果
    val resultList = new ListBuffer[ReplyResult]

    //待處理資料路徑
    val files = Array("D:\\datasets\\scala\\1.txt", "D:\\datasets\\scala\\2.txt", "D:\\datasets\\scala\\3.txt")
    //val filePath ="D:\\datasets\\scala\\1.txt"
    //遍歷待處理檔案檔案,分別啟動task處理各個檔案
    for (f <- files) {
      //創建啟動TaskActor
      val task = new Task
      task.start()
      //把待處理的資料路徑(任務)發送給task處理
      //因為需要對最終的結果進行合并,使用異步有回傳值的訊息
      val future: Future[Any] = task !! SubmitTask(f)
      //把future保存至集合中
      futureSet += future
    }

    //判斷future是否已經完成有結果,如果有結果就提取結果,future.isSet用于判斷結果是否有結果
    while (futureSet.nonEmpty) {
      //過濾出已經有結果的future
      val completed: mutable.HashSet[Future[Any]] = futureSet.filter(f => f.isSet)
      //遍歷已經完成的 提取結果
      for (c <- completed) {
        //提取結果
        val result: ReplyResult = c.apply().asInstanceOf[ReplyResult]
        //將結果添加至resultList
        resultList += result
        //把提取完結果的future從futureSet給剔除
        futureSet.remove(c)
      }
    }

    println(resultList.flatMap(r => r.result)
      .groupBy(_._1)
      .mapValues(values => values.map(x => x._2).sum))
  }
}

按照函式式編程的做法,可以精簡大量代碼:

package com.aa.actorDEMO

import scala.actors.Actor
import scala.io.Source

//優化后的簡化版本

//定義個樣例類 用于封裝提交的任務
case class SubmitTask(filePath:String)


class Task extends Actor {
  override def act(): Unit ={
    loop{
      react{
        //接收的task任務 處理資料 進行wc
        case SubmitTask(filePath) =>{
          //從檔案中讀取資料,并且讀取的資料轉換為String,底層是全文讀
          val content: String = Source.fromFile(filePath).mkString
          val result: Map[String, Int] = content.split("\r\n")
            .flatMap(_.split(" "))
            .map((_, 1))
            .groupBy(_._1)
            .mapValues(_.length)

          println(result)
        }
      }
    }
  }
}

object WordCount {
  def main(args: Array[String]): Unit = {

    //待處理資料路徑
    val filePath ="D:\\datasets\\scala\\1.txt"

    //創建啟動TaskActor
    val task = new Task
    task.start()

    //把待處理的資料路徑(任務)異步提交給task處理
    task ! SubmitTask(filePath)
  }
}

簡化后可讀性變差,但代碼很簡潔,

這里有個小細節:

在這里插入圖片描述

在這里插入圖片描述

在這里插入圖片描述

傳的引數必須是小寫字母開頭,不能大寫字母開頭,否則會報錯:Cannot resolve symbol,Scala這點做的很奇怪,,,

在這里插入圖片描述

對命令列的輸入進行WordCount事實證明也是可行的:

package com.aa

import scala.collection.mutable.ListBuffer
import scala.io.StdIn
import scala.io.Source

object WordCount_Simple {
  def main(args: Array[String]): Unit = {
    var input = ListBuffer("哈 哈 哈 哈", "哈 哈 呵 呵 呵呵 哈哈")
    val inputPath = "E:\\study\\TopN\\origin.txt"
    var inputStr:String = "哈 哈 哈 哈"
    println("輸入路徑:"+inputPath)
    val context:String = Source.fromFile(inputPath).mkString
    var wordcount_In = context.split("\r\n")
      .flatMap(_.split(" "))
      .map((_, 1))
      .groupBy(_._1)
      .map(x => (x._1, x._2.size))
      .toList.sortBy(_._2)
    println(wordcount_In)             //路徑檔案的WC

    val output = input.flatMap(_.split(" "))
      .map((_, 1))
      .groupBy(_._1)
      .map(x => (x._1, x._2.size))
      .toList.sortBy(_._2)
    println("測驗資料:"+output)
    while (true) {
      inputStr =StdIn.readLine()
      println("輸入的內容:"+inputStr)

      val input_WC: List[(String, Int)] = inputStr.split("\r\n")
        .flatMap(_.split(" "))
        .map((_, 1))
        .groupBy(_._1)
        .map(x => (x._1, x._2.size))
        .toList.sortBy(_._2)
      println(input_WC)

    }
  }
}

Scala是個好東西,但是并沒有多少人用這種冷門語言,也是很神奇,

轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/286923.html

標籤:其他

上一篇:Linux-CentOS虛擬機之間設定SSH免密登錄

下一篇:Python 有字串包含子字串的方法嗎?

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 網閘典型架構簡述

    網閘架構一般分為兩種:三主機的三系統架構網閘和雙主機的2+1架構網閘。 三主機架構分別為內端機、外端機和仲裁機。三機無論從軟體和硬體上均各自獨立。首先從硬體上來看,三機都用各自獨立的主板、記憶體及存盤設備。從軟體上來看,三機有各自獨立的作業系統。這樣能達到完全的三機獨立。對于“2+1”系統,“2”分為 ......

    uj5u.com 2020-09-10 02:00:44 more
  • 如何從xshell上傳檔案到centos linux虛擬機里

    如何從xshell上傳檔案到centos linux虛擬機里及:虛擬機CentOs下執行 yum -y install lrzsz命令,出現錯誤:鏡像無法找到軟體包 前言 一、安裝lrzsz步驟 二、上傳檔案 三、遇到的問題及解決方案 總結 前言 提示:其實很簡單,往虛擬機上安裝一個上傳檔案的工具 ......

    uj5u.com 2020-09-10 02:00:47 more
  • 一、SQLMAP入門

    一、SQLMAP入門 1、判斷是否存在注入 sqlmap.py -u 網址/id=1 id=1不可缺少。當注入點后面的引數大于兩個時。需要加雙引號, sqlmap.py -u "網址/id=1&uid=1" 2、判斷文本中的請求是否存在注入 從文本中加載http請求,SQLMAP可以從一個文本檔案中 ......

    uj5u.com 2020-09-10 02:00:50 more
  • Metasploit 簡單使用教程

    metasploit 簡單使用教程 浩先生, 2020-08-28 16:18:25 分類專欄: kail 網路安全 linux 文章標簽: linux資訊安全 編輯 著作權 metasploit 使用教程 前言 一、Metasploit是什么? 二、準備作業 三、具體步驟 前言 Msfconsole ......

    uj5u.com 2020-09-10 02:00:53 more
  • 游戲逆向之驅動層與用戶層通訊

    驅動層代碼: #pragma once #include <ntifs.h> #define add_code CTL_CODE(FILE_DEVICE_UNKNOWN,0x800,METHOD_BUFFERED,FILE_ANY_ACCESS) /* 更多游戲逆向視頻www.yxfzedu.com ......

    uj5u.com 2020-09-10 02:00:56 more
  • 北斗電力時鐘(北斗授時服務器)讓網路資料更精準

    北斗電力時鐘(北斗授時服務器)讓網路資料更精準 北斗電力時鐘(北斗授時服務器)讓網路資料更精準 京準電子科技官微——ahjzsz 近幾年,資訊技術的得了快速發展,互聯網在逐漸普及,其在人們生活和生產中都得到了廣泛應用,并且取得了不錯的應用效果。計算機網路資訊在電力系統中的應用,一方面使電力系統的運行 ......

    uj5u.com 2020-09-10 02:01:03 more
  • 【CTF】CTFHub 技能樹 彩蛋 writeup

    ?碎碎念 CTFHub:https://www.ctfhub.com/ 筆者入門CTF時時剛開始刷的是bugku的舊平臺,后來才有了CTFHub。 感覺不論是網頁UI設計,還是題目質量,賽事跟蹤,工具軟體都做得很不錯。 而且因為獨到的金幣制度的確讓人有一種想去刷題賺金幣的感覺。 個人還是非常喜歡這個 ......

    uj5u.com 2020-09-10 02:04:05 more
  • 02windows基礎操作

    我學到了一下幾點 Windows系統目錄結構與滲透的作用 常見Windows的服務詳解 Windows埠詳解 常用的Windows注冊表詳解 hacker DOS命令詳解(net user / type /md /rd/ dir /cd /net use copy、批處理 等) 利用dos命令制作 ......

    uj5u.com 2020-09-10 02:04:18 more
  • 03.Linux基礎操作

    我學到了以下幾點 01Linux系統介紹02系統安裝,密碼啊破解03Linux常用命令04LAMP 01LINUX windows: win03 8 12 16 19 配置不繁瑣 Linux:redhat,centos(紅帽社區版),Ubuntu server,suse unix:金融機構,證券,銀 ......

    uj5u.com 2020-09-10 02:04:30 more
  • 05HTML

    01HTML介紹 02頭部標簽講解03基礎標簽講解04表單標簽講解 HTML前段語言 js1.了解代碼2.根據代碼 懂得挖掘漏洞 (POST注入/XSS漏洞上傳)3.黑帽seo 白帽seo 客戶網站被黑帽植入劫持代碼如何處理4.熟悉html表單 <html><head><title>TDK標題,描述 ......

    uj5u.com 2020-09-10 02:04:36 more
最新发布
  • 2023年最新微信小程式抓包教程

    01 開門見山 隔一個月發一篇文章,不過分。 首先回顧一下《微信系結手機號資料庫被脫庫事件》,我也是第一時間得知了這個訊息,然后跟蹤了整件事情的經過。下面是這起事件的相關截圖以及近日流出的一萬條資料樣本: 個人認為這件事也沒什么,還不如關注一下之前45億快遞資料查詢渠道疑似在近日復活的訊息。 訊息是 ......

    uj5u.com 2023-04-20 08:48:24 more
  • web3 產品介紹:metamask 錢包 使用最多的瀏覽器插件錢包

    Metamask錢包是一種基于區塊鏈技術的數字貨幣錢包,它允許用戶在安全、便捷的環境下管理自己的加密資產。Metamask錢包是以太坊生態系統中最流行的錢包之一,它具有易于使用、安全性高和功能強大等優點。 本文將詳細介紹Metamask錢包的功能和使用方法。 一、 Metamask錢包的功能 數字資 ......

    uj5u.com 2023-04-20 08:47:46 more
  • vulnhub_Earth

    前言 靶機地址->>>vulnhub_Earth 攻擊機ip:192.168.20.121 靶機ip:192.168.20.122 參考文章 https://www.cnblogs.com/Jing-X/archive/2022/04/03/16097695.html https://www.cnb ......

    uj5u.com 2023-04-20 07:46:20 more
  • 從4k到42k,軟體測驗工程師的漲薪史,給我看哭了

    清明節一過,盲猜大家已經無心上班,在數著日子準備過五一,但一想到銀行卡里的余額……瞬間心情就不美麗了。最近,2023年高校畢業生就業調查顯示,本科畢業月平均起薪為5825元。調查一出,便有很多同學表示自己又被平均了。看著這一資料,不免讓人想到前不久中國青年報的一項調查:近六成大學生認為畢業10年內會 ......

    uj5u.com 2023-04-20 07:44:00 more
  • 最新版本 Stable Diffusion 開源 AI 繪畫工具之中文自動提詞篇

    🎈 標簽生成器 由于輸入正向提示詞 prompt 和反向提示詞 negative prompt 都是使用英文,所以對學習母語的我們非常不友好 使用網址:https://tinygeeker.github.io/p/ai-prompt-generator 這個網址是為了讓大家在使用 AI 繪畫的時候 ......

    uj5u.com 2023-04-20 07:43:36 more
  • 漫談前端自動化測驗演進之路及測驗工具分析

    隨著前端技術的不斷發展和應用程式的日益復雜,前端自動化測驗也在不斷演進。隨著 Web 應用程式變得越來越復雜,自動化測驗的需求也越來越高。如今,自動化測驗已經成為 Web 應用程式開發程序中不可或缺的一部分,它們可以幫助開發人員更快地發現和修復錯誤,提高應用程式的性能和可靠性。 ......

    uj5u.com 2023-04-20 07:43:16 more
  • CANN開發實踐:4個DVPP記憶體問題的典型案例解讀

    摘要:由于DVPP媒體資料處理功能對存放輸入、輸出資料的記憶體有更高的要求(例如,記憶體首地址128位元組對齊),因此需呼叫專用的記憶體申請介面,那么本期就分享幾個關于DVPP記憶體問題的典型案例,并給出原因分析及解決方法。 本文分享自華為云社區《FAQ_DVPP記憶體問題案例》,作者:昇騰CANN。 DVPP ......

    uj5u.com 2023-04-20 07:43:03 more
  • msf學習

    msf學習 以kali自帶的msf為例 一、msf核心模塊與功能 msf模塊都放在/usr/share/metasploit-framework/modules目錄下 1、auxiliary 輔助模塊,輔助滲透(埠掃描、登錄密碼爆破、漏洞驗證等) 2、encoders 編碼器模塊,主要包含各種編碼 ......

    uj5u.com 2023-04-20 07:42:59 more
  • Halcon軟體安裝與界面簡介

    1. 下載Halcon17版本到到本地 2. 雙擊安裝包后 3. 步驟如下 1.2 Halcon軟體安裝 界面分為四大塊 1. Halcon的五個助手 1) 影像采集助手:與相機連接,設定相機引數,采集影像 2) 標定助手:九點標定或是其它的標定,生成標定檔案及內參外參,可以將像素單位轉換為長度單位 ......

    uj5u.com 2023-04-20 07:42:17 more
  • 在MacOS下使用Unity3D開發游戲

    第一次發博客,先發一下我的游戲開發環境吧。 去年2月份買了一臺MacBookPro2021 M1pro(以下簡稱mbp),這一年來一直在用mbp開發游戲。我大致分享一下我的開發工具以及使用體驗。 1、Unity 官網鏈接: https://unity.cn/releases 我一般使用的Apple ......

    uj5u.com 2023-04-20 07:40:19 more