Scala併發編程WordCount案例

WordCount案例

接下來,我們要使用Actor併發編程模型實現多文件的單詞統計。



案例介紹

給定幾個文本文件(文本文件都是以空格分隔的),使用Actor併發編程來統計單詞的數量

思路分析

在這裏插入圖片描述

實現思路

  1. MainActor獲取要進行單詞統計的文件
  2. 根據文件數量創建對應的WordCountActor
  3. 將文件名封裝爲消息發送給WordCountActor
  4. WordCountActor接收消息,並統計單個文件的單詞計數
  5. 將單詞計數結果發送給MainActor
  6. MainActor等待所有的WordCountActor都已經成功返回消息,然後進行結果合併

步驟1 | 獲取文件列表

實現思路

在main方法中讀取指定目錄(${project_root_dir}/data/)下的所有文件,並打印所有的文件名


實現步驟

  1. 創建用於測試的數據文件
  2. 加載工程根目錄,獲取到所有文件
  3. 將每一個文件名,添加目錄路徑
  4. 打印所有文件名

參考代碼

// 1. MainActor獲取要進行單詞統計的文件
val DIR_PATH = "./data/"
val dataDir = new File(DIR_PATH)

// 讀取所有data目錄下的所有文件
println("對以下文件進行單詞統計:")
// 構建文件列表
val fileList = dataDir.list().toList.map(DIR_PATH + _)
println(fileList)

步驟2 | 創建WordCountActor

實現思路

根據文件數量創建WordCountActor,爲了方便後續發送消息給Actor,將每個Actor與文件名關聯在一起


實現步驟

  1. 創建WordCountActor
  2. 將文件列表轉換爲WordCountActor
  3. 爲了後續方便發送消息給Actor,將Actor列表和文件列表拉鍊到一起
  4. 打印測試

參考代碼

MainActor.scala

// 2. 根據文件數量創建對應的WordCountActor
val actorList = fileList.map {
    x => new WordCountActor
}

// 將Actor和文件名列表建立爲元組
val actorWithFileList: List[(WordCountActor, String)] = actorList.zip(fileList)

WordCountActor.scala

class WordCountActor extends Actor{
  override def act(): Unit = {
  }
}

步驟3 | 啓動Actor/發送/接收任務消息

實現思路

啓動所有WordCountActor,併發送單詞統計任務消息給每個WordCountActor

[!NOTE]

此處應發送異步有返回消息


實現步驟

  1. 創建一個WordCountTask樣例類消息,封裝要進行單詞計數的文件名
  2. 啓動所有WordCountTask,併發送異步有返回消息
  3. 獲取到所有的WordCount中獲取到的消息(封裝到一個Future列表中)
  4. 在WordCountActor中接收並打印消息

參考代碼

MainActor.scala

// 3. 將文件名封裝爲消息發送給WordCountActor,並獲取到異步返回結果
val futureList = actorWithFileList.map {
    // tuple爲Actor和文件名
    tuple =>
    // 啓動actor
    tuple._1.start()
    // 發送任務消息
    tuple._1 !! WordCountTask(tuple._2)
}

MessagePackage.scala

/**
  * 單詞統計任務消息
  * @param fileName 文件名
  */
case class WordCountTask(fileName:String)

WordCountActor.scala

loop {
    receive {
        // 接收單詞統計任務消息
        case WordCountTask(fileName) => {
            println("接收到消息:" + fileName)
        }
    }
}

步驟4 | 消息統計文件單詞計數

實現思路

讀取文件文本,並統計出來單詞的數量。例如:

(hadoop, 3), (spark, 1)...

實現步驟

  1. 讀取文件內容,並轉換爲列表
  2. 按照空格切割文本,並轉換爲一個一個的單詞
  3. 爲了方便進行計數,將單詞轉換爲元組
  4. 按照單詞進行分組,然後再進行聚合統計
  5. 打印聚合統計結果

參考代碼

WordCountActor.scala

// 4. 統計單個文件的單詞計數
val iter: Iterator[String] = Source.fromFile(fileName).getLines()
// [第一行] hadoop hadoop
// [第二行] hadoop spark
val lineList = iter.toList
// [單詞列表] hadoop, hadoop, hadoop, spark
val wordList: List[String] = lineList.flatMap(_.split(" "))
// 將單詞轉換爲元組
// [元組列表] (hadoop, 1), (hadoop, 1), (hadoop, 1), (spark, 1)
val tupleList = wordList.map(_ -> 1)
// 按照單詞進行分組
// [單詞分組] = {hadoop->List(hadoop->1, hadoop->1, hadoop->1), spark->List(spark ->1)}
val grouped: Map[String, List[(String, Int)]] = tupleList.groupBy(_._1)
// 將分組內的數據進行聚合
// [單詞計數] = (hadoop, 3), (spark, 1)
val wordCount: Map[String, Int] = grouped.map {
    tuple =>
    // 單詞
    val word = tuple._1
    // 進行計數
    // 獲取到所有的單詞數量,然後進行累加
    val total = tuple._2.map(_._2).sum
    word -> total
}
println(wordCount)

步驟5 | 封裝單詞計數結果回覆給MainActor

實現思路

  • 將單詞計數的結果封裝爲一個樣例類消息,併發送給MainActor
  • MainActor等待所有WordCount均已返回後獲取到每個WordCountActor單詞計算後的結果

實現步驟

  1. 定義一個樣例類封裝單詞計數結果
  2. 將單詞計數結果發送給MainActor
  3. MainActor中檢測所有WordActor是否均已返回,如果均已返回,則獲取並轉換結果
  4. 打印結果

參考代碼

MessagePackage.scala

/**
  * 單詞統計結果
  * @param wordCount 單詞計數
  */
case class WordCountResult(wordCount: Map[String, Int])

WordCountActor.scala

// 5. 將單詞計數結果回覆給MainActor
sender ! WordCountResult(wordCount)

MainActor.scala

// 等待所有Actor都已經返回
while(futureList.filter(_.isSet).size != fileList.size){}
// MainActor等待所有的WordCountActor都已經成功返回消息,然後進行結果合併
val resultList: List[Map[String, Int]] = futureList.map(_.apply.asInstanceOf[WordCountResult].wordCount)
println("接收到所有統計結果:" + resultList)

步驟6 | 結果合併

實現思路

對接收到的所有單詞計數進行合併。因爲該部分已經在WordCountActor已經編寫過,所以抽取這部分一樣的代碼到一個工具類中,再調用合併得到最終結果

實現步驟

  1. 創建一個用於單詞合併的工具類
  2. 抽取重複代碼爲一個方法
  3. 在MainActor調用該合併方法,計算得到最終結果,並打印

參考代碼

WordCountUtil.scala

  /**
    * 單詞分組統計
    * @param wordCountList 單詞計數列表
    * @return 分組聚合結果
    */
  def reduce(wordCountList:List[(String, Int)]) = {
    // 按照單詞進行分組
    // [單詞分組] = {hadoop->List(hadoop->1, hadoop->1, hadoop->1), spark->List(spark ->1)}
    val grouped: Map[String, List[(String, Int)]] = wordCountList.groupBy(_._1)
    // 將分組內的數據進行聚合
    // [單詞計數] = (hadoop, 3), (spark, 1)
    val wordCount: Map[String, Int] = grouped.map {
      tuple =>
        // 單詞
        val word = tuple._1
        // 進行計數
        // 獲取到所有的單詞數量,然後進行累加
        val total = tuple._2.map(_._2).sum
        word -> total
    }
    wordCount
  }

MainActor.scala

// 扁平化後再聚合計算
val result: Map[String, Int] = WordCountUtil.reduce(resultList.flatten)

println("最終結果:" + result)
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章